basecamp-sdk 0.4.0 → 0.6.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 +4 -4
- data/README.md +2 -2
- data/lib/basecamp/ambiguous_error.rb +23 -0
- data/lib/basecamp/api_error.rb +27 -0
- data/lib/basecamp/auth_error.rb +16 -0
- data/lib/basecamp/auth_strategy.rb +0 -18
- data/lib/basecamp/bearer_auth.rb +21 -0
- data/lib/basecamp/client.rb +126 -0
- data/lib/basecamp/download_result.rb +10 -0
- data/lib/basecamp/error.rb +86 -0
- data/lib/basecamp/error_code.rb +16 -0
- data/lib/basecamp/exit_code.rb +17 -0
- data/lib/basecamp/forbidden_error.rb +20 -0
- data/lib/basecamp/generated/metadata.json +12 -1
- data/lib/basecamp/{services → generated/services}/authorization_service.rb +1 -1
- data/lib/basecamp/generated/services/automation_service.rb +19 -0
- data/lib/basecamp/{services → generated/services}/base_service.rb +0 -1
- data/lib/basecamp/generated/services/card_steps_service.rb +9 -0
- data/lib/basecamp/generated/services/messages_service.rb +5 -2
- data/lib/basecamp/generated/services/search_service.rb +1 -1
- data/lib/basecamp/generated/services/tools_service.rb +3 -2
- data/lib/basecamp/generated/types.rb +3 -3
- data/lib/basecamp/http.rb +22 -13
- data/lib/basecamp/network_error.rb +16 -0
- data/lib/basecamp/not_found_error.rb +20 -0
- data/lib/basecamp/oauth/config.rb +30 -0
- data/lib/basecamp/oauth/discovery.rb +9 -41
- data/lib/basecamp/oauth/exchange.rb +20 -114
- data/lib/basecamp/oauth/exchange_request.rb +36 -0
- data/lib/basecamp/oauth/{errors.rb → oauth_error.rb} +1 -1
- data/lib/basecamp/oauth/refresh_request.rb +30 -0
- data/lib/basecamp/oauth/token.rb +52 -0
- data/lib/basecamp/oauth.rb +40 -8
- data/lib/basecamp/operation_info.rb +0 -7
- data/lib/basecamp/operation_result.rb +10 -0
- data/lib/basecamp/rate_limit_error.rb +19 -0
- data/lib/basecamp/security.rb +1 -1
- data/lib/basecamp/usage_error.rb +10 -0
- data/lib/basecamp/validation_error.rb +15 -0
- data/lib/basecamp/version.rb +1 -1
- data/lib/basecamp/webhooks/rack_middleware.rb +0 -2
- data/lib/basecamp/webhooks/receiver.rb +0 -4
- data/lib/basecamp/webhooks/verification_error.rb +7 -0
- data/lib/basecamp.rb +62 -22
- data/scripts/generate-services.rb +3 -3
- metadata +26 -43
- data/lib/basecamp/errors.rb +0 -294
- data/lib/basecamp/oauth/types.rb +0 -133
- data/lib/basecamp/services/attachments_service.rb +0 -33
- data/lib/basecamp/services/campfires_service.rb +0 -141
- data/lib/basecamp/services/card_columns_service.rb +0 -106
- data/lib/basecamp/services/card_steps_service.rb +0 -86
- data/lib/basecamp/services/card_tables_service.rb +0 -23
- data/lib/basecamp/services/cards_service.rb +0 -93
- data/lib/basecamp/services/checkins_service.rb +0 -127
- data/lib/basecamp/services/client_approvals_service.rb +0 -33
- data/lib/basecamp/services/client_correspondences_service.rb +0 -33
- data/lib/basecamp/services/client_replies_service.rb +0 -35
- data/lib/basecamp/services/comments_service.rb +0 -63
- data/lib/basecamp/services/documents_service.rb +0 -74
- data/lib/basecamp/services/events_service.rb +0 -27
- data/lib/basecamp/services/forwards_service.rb +0 -80
- data/lib/basecamp/services/lineup_service.rb +0 -67
- data/lib/basecamp/services/message_boards_service.rb +0 -24
- data/lib/basecamp/services/message_types_service.rb +0 -79
- data/lib/basecamp/services/messages_service.rb +0 -133
- data/lib/basecamp/services/people_service.rb +0 -73
- data/lib/basecamp/services/projects_service.rb +0 -67
- data/lib/basecamp/services/recordings_service.rb +0 -127
- data/lib/basecamp/services/reports_service.rb +0 -80
- data/lib/basecamp/services/schedules_service.rb +0 -156
- data/lib/basecamp/services/search_service.rb +0 -36
- data/lib/basecamp/services/subscriptions_service.rb +0 -67
- data/lib/basecamp/services/templates_service.rb +0 -96
- data/lib/basecamp/services/timeline_service.rb +0 -62
- data/lib/basecamp/services/timesheet_service.rb +0 -68
- data/lib/basecamp/services/todolist_groups_service.rb +0 -100
- data/lib/basecamp/services/todolists_service.rb +0 -104
- data/lib/basecamp/services/todos_service.rb +0 -156
- data/lib/basecamp/services/todosets_service.rb +0 -23
- data/lib/basecamp/services/tools_service.rb +0 -89
- data/lib/basecamp/services/uploads_service.rb +0 -84
- data/lib/basecamp/services/vaults_service.rb +0 -84
- data/lib/basecamp/services/webhooks_service.rb +0 -88
data/lib/basecamp/errors.rb
DELETED
|
@@ -1,294 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Error types and codes for the Basecamp SDK.
|
|
4
|
-
module Basecamp
|
|
5
|
-
# Error codes for API responses
|
|
6
|
-
module ErrorCode
|
|
7
|
-
USAGE = "usage"
|
|
8
|
-
NOT_FOUND = "not_found"
|
|
9
|
-
AUTH = "auth_required"
|
|
10
|
-
FORBIDDEN = "forbidden"
|
|
11
|
-
RATE_LIMIT = "rate_limit"
|
|
12
|
-
NETWORK = "network"
|
|
13
|
-
API = "api_error"
|
|
14
|
-
AMBIGUOUS = "ambiguous"
|
|
15
|
-
VALIDATION = "validation"
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
# Exit codes for CLI tools
|
|
19
|
-
module ExitCode
|
|
20
|
-
OK = 0
|
|
21
|
-
USAGE = 1
|
|
22
|
-
NOT_FOUND = 2
|
|
23
|
-
AUTH = 3
|
|
24
|
-
FORBIDDEN = 4
|
|
25
|
-
RATE_LIMIT = 5
|
|
26
|
-
NETWORK = 6
|
|
27
|
-
API = 7
|
|
28
|
-
AMBIGUOUS = 8
|
|
29
|
-
VALIDATION = 9
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
# Base error class for all Basecamp SDK errors.
|
|
33
|
-
# Provides structured error handling with codes, hints, and CLI exit codes.
|
|
34
|
-
#
|
|
35
|
-
# @example Catching errors
|
|
36
|
-
# begin
|
|
37
|
-
# client.projects.list
|
|
38
|
-
# rescue Basecamp::Error => e
|
|
39
|
-
# puts "#{e.code}: #{e.message}"
|
|
40
|
-
# puts "Hint: #{e.hint}" if e.hint
|
|
41
|
-
# exit e.exit_code
|
|
42
|
-
# end
|
|
43
|
-
class Error < StandardError
|
|
44
|
-
# @return [String] error category code
|
|
45
|
-
attr_reader :code
|
|
46
|
-
|
|
47
|
-
# @return [String, nil] user-friendly hint for resolving the error
|
|
48
|
-
attr_reader :hint
|
|
49
|
-
|
|
50
|
-
# @return [Integer, nil] HTTP status code that caused the error
|
|
51
|
-
attr_reader :http_status
|
|
52
|
-
|
|
53
|
-
# @return [Boolean] whether the operation can be retried
|
|
54
|
-
attr_reader :retryable
|
|
55
|
-
|
|
56
|
-
# @return [Integer, nil] seconds to wait before retrying (for rate limits)
|
|
57
|
-
attr_reader :retry_after
|
|
58
|
-
|
|
59
|
-
# @return [String, nil] X-Request-Id from the response
|
|
60
|
-
attr_reader :request_id
|
|
61
|
-
|
|
62
|
-
# @return [Exception, nil] original error that caused this error
|
|
63
|
-
attr_reader :cause
|
|
64
|
-
|
|
65
|
-
# @param code [String] error category code
|
|
66
|
-
# @param message [String] error message
|
|
67
|
-
# @param hint [String, nil] user-friendly hint
|
|
68
|
-
# @param http_status [Integer, nil] HTTP status code
|
|
69
|
-
# @param retryable [Boolean] whether operation can be retried
|
|
70
|
-
# @param retry_after [Integer, nil] seconds to wait before retry
|
|
71
|
-
# @param request_id [String, nil] X-Request-Id from response
|
|
72
|
-
# @param cause [Exception, nil] underlying cause
|
|
73
|
-
def initialize(code:, message:, hint: nil, http_status: nil, retryable: false, retry_after: nil, request_id: nil, cause: nil)
|
|
74
|
-
super(message)
|
|
75
|
-
@code = code
|
|
76
|
-
@hint = hint
|
|
77
|
-
@http_status = http_status
|
|
78
|
-
@retryable = retryable
|
|
79
|
-
@retry_after = retry_after
|
|
80
|
-
@request_id = request_id
|
|
81
|
-
@cause = cause
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
# Returns the exit code for CLI applications.
|
|
85
|
-
# @return [Integer]
|
|
86
|
-
def exit_code
|
|
87
|
-
self.class.exit_code_for(@code)
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
# Returns whether this error can be retried.
|
|
91
|
-
# @return [Boolean]
|
|
92
|
-
def retryable?
|
|
93
|
-
@retryable
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
# Maps error codes to exit codes.
|
|
97
|
-
# @param code [String]
|
|
98
|
-
# @return [Integer]
|
|
99
|
-
def self.exit_code_for(code)
|
|
100
|
-
case code
|
|
101
|
-
when ErrorCode::USAGE then ExitCode::USAGE
|
|
102
|
-
when ErrorCode::NOT_FOUND then ExitCode::NOT_FOUND
|
|
103
|
-
when ErrorCode::AUTH then ExitCode::AUTH
|
|
104
|
-
when ErrorCode::FORBIDDEN then ExitCode::FORBIDDEN
|
|
105
|
-
when ErrorCode::RATE_LIMIT then ExitCode::RATE_LIMIT
|
|
106
|
-
when ErrorCode::NETWORK then ExitCode::NETWORK
|
|
107
|
-
when ErrorCode::API then ExitCode::API
|
|
108
|
-
when ErrorCode::AMBIGUOUS then ExitCode::AMBIGUOUS
|
|
109
|
-
when ErrorCode::VALIDATION then ExitCode::VALIDATION
|
|
110
|
-
else ExitCode::API
|
|
111
|
-
end
|
|
112
|
-
end
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
# Raised when there's a usage error (invalid arguments, missing config).
|
|
116
|
-
class UsageError < Error
|
|
117
|
-
def initialize(message, hint: nil)
|
|
118
|
-
super(code: ErrorCode::USAGE, message: message, hint: hint)
|
|
119
|
-
end
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
# Raised when a resource is not found (404).
|
|
123
|
-
class NotFoundError < Error
|
|
124
|
-
def initialize(resource, identifier, hint: nil)
|
|
125
|
-
super(
|
|
126
|
-
code: ErrorCode::NOT_FOUND,
|
|
127
|
-
message: "#{resource} not found: #{identifier}",
|
|
128
|
-
hint: hint,
|
|
129
|
-
http_status: 404
|
|
130
|
-
)
|
|
131
|
-
end
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
# Raised when authentication fails (401).
|
|
135
|
-
class AuthError < Error
|
|
136
|
-
def initialize(message = "Authentication required", hint: nil, cause: nil)
|
|
137
|
-
super(
|
|
138
|
-
code: ErrorCode::AUTH,
|
|
139
|
-
message: message,
|
|
140
|
-
hint: hint || "Check your access token or refresh it if expired",
|
|
141
|
-
http_status: 401,
|
|
142
|
-
cause: cause
|
|
143
|
-
)
|
|
144
|
-
end
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
# Raised when access is denied (403).
|
|
148
|
-
class ForbiddenError < Error
|
|
149
|
-
def initialize(message = "Access denied", hint: nil)
|
|
150
|
-
super(
|
|
151
|
-
code: ErrorCode::FORBIDDEN,
|
|
152
|
-
message: message,
|
|
153
|
-
hint: hint || "You do not have permission to access this resource",
|
|
154
|
-
http_status: 403
|
|
155
|
-
)
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
# Creates a forbidden error due to insufficient OAuth scope.
|
|
159
|
-
def self.insufficient_scope
|
|
160
|
-
new("Access denied: insufficient scope", hint: "Re-authenticate with full scope")
|
|
161
|
-
end
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
# Raised when rate limited (429).
|
|
165
|
-
class RateLimitError < Error
|
|
166
|
-
def initialize(retry_after: nil, cause: nil)
|
|
167
|
-
hint = retry_after ? "Try again in #{retry_after} seconds" : "Please slow down requests"
|
|
168
|
-
super(
|
|
169
|
-
code: ErrorCode::RATE_LIMIT,
|
|
170
|
-
message: "Rate limit exceeded",
|
|
171
|
-
hint: hint,
|
|
172
|
-
http_status: 429,
|
|
173
|
-
retryable: true,
|
|
174
|
-
retry_after: retry_after,
|
|
175
|
-
cause: cause
|
|
176
|
-
)
|
|
177
|
-
end
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
# Raised when there's a network error (connection, timeout, DNS).
|
|
181
|
-
class NetworkError < Error
|
|
182
|
-
def initialize(message = "Network error", cause: nil)
|
|
183
|
-
super(
|
|
184
|
-
code: ErrorCode::NETWORK,
|
|
185
|
-
message: message,
|
|
186
|
-
hint: cause&.message || "Check your network connection",
|
|
187
|
-
retryable: true,
|
|
188
|
-
cause: cause
|
|
189
|
-
)
|
|
190
|
-
end
|
|
191
|
-
end
|
|
192
|
-
|
|
193
|
-
# Raised for generic API errors.
|
|
194
|
-
class APIError < Error
|
|
195
|
-
def initialize(message, http_status: nil, hint: nil, retryable: false, cause: nil)
|
|
196
|
-
super(
|
|
197
|
-
code: ErrorCode::API,
|
|
198
|
-
message: message,
|
|
199
|
-
hint: hint,
|
|
200
|
-
http_status: http_status,
|
|
201
|
-
retryable: retryable,
|
|
202
|
-
cause: cause
|
|
203
|
-
)
|
|
204
|
-
end
|
|
205
|
-
|
|
206
|
-
# Creates an APIError from an HTTP status code.
|
|
207
|
-
# @param status [Integer] HTTP status code
|
|
208
|
-
# @param message [String, nil] optional error message
|
|
209
|
-
# @return [APIError]
|
|
210
|
-
def self.from_status(status, message = nil)
|
|
211
|
-
message ||= "Request failed (HTTP #{status})"
|
|
212
|
-
retryable = status >= 500 && status < 600
|
|
213
|
-
new(message, http_status: status, retryable: retryable)
|
|
214
|
-
end
|
|
215
|
-
end
|
|
216
|
-
|
|
217
|
-
# Raised when a name/identifier matches multiple resources.
|
|
218
|
-
class AmbiguousError < Error
|
|
219
|
-
# @return [Array<String>] list of matching resources
|
|
220
|
-
attr_reader :matches
|
|
221
|
-
|
|
222
|
-
def initialize(resource, matches: [])
|
|
223
|
-
@matches = matches
|
|
224
|
-
hint = if matches.any? && matches.length <= 5
|
|
225
|
-
"Did you mean: #{matches.join(", ")}"
|
|
226
|
-
else
|
|
227
|
-
"Be more specific"
|
|
228
|
-
end
|
|
229
|
-
super(
|
|
230
|
-
code: ErrorCode::AMBIGUOUS,
|
|
231
|
-
message: "Ambiguous #{resource}",
|
|
232
|
-
hint: hint
|
|
233
|
-
)
|
|
234
|
-
end
|
|
235
|
-
end
|
|
236
|
-
|
|
237
|
-
# Raised for validation errors (400, 422).
|
|
238
|
-
class ValidationError < Error
|
|
239
|
-
def initialize(message, hint: nil, http_status: 400)
|
|
240
|
-
super(
|
|
241
|
-
code: ErrorCode::VALIDATION,
|
|
242
|
-
message: message,
|
|
243
|
-
hint: hint,
|
|
244
|
-
http_status: http_status
|
|
245
|
-
)
|
|
246
|
-
end
|
|
247
|
-
end
|
|
248
|
-
|
|
249
|
-
# Maps an HTTP response to the appropriate error class.
|
|
250
|
-
#
|
|
251
|
-
# @param status [Integer] HTTP status code
|
|
252
|
-
# @param body [String, nil] response body (will attempt JSON parse)
|
|
253
|
-
# @param retry_after [Integer, nil] Retry-After header value
|
|
254
|
-
# @return [Error]
|
|
255
|
-
def self.error_from_response(status, body = nil, retry_after: nil)
|
|
256
|
-
message = parse_error_message(body) || "Request failed"
|
|
257
|
-
|
|
258
|
-
case status
|
|
259
|
-
when 400, 422
|
|
260
|
-
ValidationError.new(message, http_status: status)
|
|
261
|
-
when 401
|
|
262
|
-
AuthError.new(message)
|
|
263
|
-
when 403
|
|
264
|
-
ForbiddenError.new(message)
|
|
265
|
-
when 404
|
|
266
|
-
NotFoundError.new("Resource", "unknown")
|
|
267
|
-
when 429
|
|
268
|
-
RateLimitError.new(retry_after: retry_after)
|
|
269
|
-
when 500
|
|
270
|
-
APIError.new("Server error (500)", http_status: 500, retryable: true)
|
|
271
|
-
when 502, 503, 504
|
|
272
|
-
APIError.new("Gateway error (#{status})", http_status: status, retryable: true)
|
|
273
|
-
else
|
|
274
|
-
APIError.from_status(status, message)
|
|
275
|
-
end
|
|
276
|
-
end
|
|
277
|
-
|
|
278
|
-
# Parses error message from response body.
|
|
279
|
-
# @param body [String, nil]
|
|
280
|
-
# @return [String, nil]
|
|
281
|
-
def self.parse_error_message(body)
|
|
282
|
-
return nil if body.nil? || body.empty?
|
|
283
|
-
|
|
284
|
-
# Guard against oversized error bodies before parsing
|
|
285
|
-
Basecamp::Security.check_body_size!(body, Basecamp::Security::MAX_ERROR_BODY_BYTES, "Error")
|
|
286
|
-
|
|
287
|
-
data = JSON.parse(body)
|
|
288
|
-
msg = data["error"] || data["message"]
|
|
289
|
-
msg ? Basecamp::Security.truncate(msg) : nil
|
|
290
|
-
rescue JSON::ParserError, Basecamp::APIError
|
|
291
|
-
# Return nil on parse errors or oversized bodies to preserve normal error type mapping
|
|
292
|
-
nil
|
|
293
|
-
end
|
|
294
|
-
end
|
data/lib/basecamp/oauth/types.rb
DELETED
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Basecamp
|
|
4
|
-
module Oauth
|
|
5
|
-
# OAuth 2 server configuration from discovery endpoint.
|
|
6
|
-
#
|
|
7
|
-
# @attr issuer [String] The authorization server's issuer identifier
|
|
8
|
-
# @attr authorization_endpoint [String] URL of the authorization endpoint
|
|
9
|
-
# @attr token_endpoint [String] URL of the token endpoint
|
|
10
|
-
# @attr registration_endpoint [String, nil] URL of the dynamic client registration endpoint
|
|
11
|
-
# @attr scopes_supported [Array<String>, nil] List of OAuth 2 scopes supported
|
|
12
|
-
Config = Data.define(
|
|
13
|
-
:issuer,
|
|
14
|
-
:authorization_endpoint,
|
|
15
|
-
:token_endpoint,
|
|
16
|
-
:registration_endpoint,
|
|
17
|
-
:scopes_supported
|
|
18
|
-
) do
|
|
19
|
-
def initialize(
|
|
20
|
-
issuer:,
|
|
21
|
-
authorization_endpoint:,
|
|
22
|
-
token_endpoint:,
|
|
23
|
-
registration_endpoint: nil,
|
|
24
|
-
scopes_supported: nil
|
|
25
|
-
)
|
|
26
|
-
super
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
# OAuth 2 access token response.
|
|
31
|
-
#
|
|
32
|
-
# @attr access_token [String] The access token string
|
|
33
|
-
# @attr token_type [String] Token type (usually "Bearer")
|
|
34
|
-
# @attr refresh_token [String, nil] The refresh token string
|
|
35
|
-
# @attr expires_in [Integer, nil] Lifetime of the access token in seconds
|
|
36
|
-
# @attr expires_at [Time, nil] Calculated expiration time
|
|
37
|
-
# @attr scope [String, nil] OAuth scope granted
|
|
38
|
-
Token = Data.define(
|
|
39
|
-
:access_token,
|
|
40
|
-
:token_type,
|
|
41
|
-
:refresh_token,
|
|
42
|
-
:expires_in,
|
|
43
|
-
:expires_at,
|
|
44
|
-
:scope
|
|
45
|
-
) do
|
|
46
|
-
def initialize(
|
|
47
|
-
access_token:,
|
|
48
|
-
token_type: "Bearer",
|
|
49
|
-
refresh_token: nil,
|
|
50
|
-
expires_in: nil,
|
|
51
|
-
expires_at: nil,
|
|
52
|
-
scope: nil
|
|
53
|
-
)
|
|
54
|
-
# Calculate expires_at from expires_in if not provided
|
|
55
|
-
calculated_expires_at = expires_at || (expires_in ? Time.now + expires_in : nil)
|
|
56
|
-
super(
|
|
57
|
-
access_token: access_token,
|
|
58
|
-
token_type: token_type,
|
|
59
|
-
refresh_token: refresh_token,
|
|
60
|
-
expires_in: expires_in,
|
|
61
|
-
expires_at: calculated_expires_at,
|
|
62
|
-
scope: scope
|
|
63
|
-
)
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
# Checks if the token is expired or about to expire.
|
|
67
|
-
#
|
|
68
|
-
# @param buffer_seconds [Integer] Buffer time before actual expiration (default: 60)
|
|
69
|
-
# @return [Boolean] true if expired or will expire within buffer time
|
|
70
|
-
def expired?(buffer_seconds = 60)
|
|
71
|
-
return false unless expires_at
|
|
72
|
-
|
|
73
|
-
Time.now + buffer_seconds >= expires_at
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
# Parameters for exchanging an authorization code for tokens.
|
|
78
|
-
#
|
|
79
|
-
# @attr token_endpoint [String] URL of the token endpoint
|
|
80
|
-
# @attr code [String] The authorization code received from the authorization server
|
|
81
|
-
# @attr redirect_uri [String] The redirect URI used in the authorization request
|
|
82
|
-
# @attr client_id [String] The client identifier
|
|
83
|
-
# @attr client_secret [String, nil] The client secret (optional for public clients)
|
|
84
|
-
# @attr code_verifier [String, nil] PKCE code verifier (optional)
|
|
85
|
-
# @attr use_legacy_format [Boolean] Use Launchpad's non-standard token format
|
|
86
|
-
ExchangeRequest = Data.define(
|
|
87
|
-
:token_endpoint,
|
|
88
|
-
:code,
|
|
89
|
-
:redirect_uri,
|
|
90
|
-
:client_id,
|
|
91
|
-
:client_secret,
|
|
92
|
-
:code_verifier,
|
|
93
|
-
:use_legacy_format
|
|
94
|
-
) do
|
|
95
|
-
def initialize(
|
|
96
|
-
token_endpoint:,
|
|
97
|
-
code:,
|
|
98
|
-
redirect_uri:,
|
|
99
|
-
client_id:,
|
|
100
|
-
client_secret: nil,
|
|
101
|
-
code_verifier: nil,
|
|
102
|
-
use_legacy_format: false
|
|
103
|
-
)
|
|
104
|
-
super
|
|
105
|
-
end
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
# Parameters for refreshing an access token.
|
|
109
|
-
#
|
|
110
|
-
# @attr token_endpoint [String] URL of the token endpoint
|
|
111
|
-
# @attr refresh_token [String] The refresh token
|
|
112
|
-
# @attr client_id [String, nil] The client identifier (optional)
|
|
113
|
-
# @attr client_secret [String, nil] The client secret (optional)
|
|
114
|
-
# @attr use_legacy_format [Boolean] Use Launchpad's non-standard token format
|
|
115
|
-
RefreshRequest = Data.define(
|
|
116
|
-
:token_endpoint,
|
|
117
|
-
:refresh_token,
|
|
118
|
-
:client_id,
|
|
119
|
-
:client_secret,
|
|
120
|
-
:use_legacy_format
|
|
121
|
-
) do
|
|
122
|
-
def initialize(
|
|
123
|
-
token_endpoint:,
|
|
124
|
-
refresh_token:,
|
|
125
|
-
client_id: nil,
|
|
126
|
-
client_secret: nil,
|
|
127
|
-
use_legacy_format: false
|
|
128
|
-
)
|
|
129
|
-
super
|
|
130
|
-
end
|
|
131
|
-
end
|
|
132
|
-
end
|
|
133
|
-
end
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "uri"
|
|
4
|
-
|
|
5
|
-
module Basecamp
|
|
6
|
-
module Services
|
|
7
|
-
# Service for attachment operations.
|
|
8
|
-
#
|
|
9
|
-
# Attachments are used to upload files that can be embedded in rich text
|
|
10
|
-
# content like messages, comments, and documents. After uploading, you
|
|
11
|
-
# receive an attachable_sgid that can be used to embed the file in HTML.
|
|
12
|
-
#
|
|
13
|
-
# @example Upload a file
|
|
14
|
-
# attachment = account.attachments.create(
|
|
15
|
-
# filename: "report.pdf",
|
|
16
|
-
# content_type: "application/pdf",
|
|
17
|
-
# data: file_content
|
|
18
|
-
# )
|
|
19
|
-
# # Use in HTML: <bc-attachment sgid="#{attachment["attachable_sgid"]}"></bc-attachment>
|
|
20
|
-
class AttachmentsService < BaseService
|
|
21
|
-
# Creates an attachment by uploading a file.
|
|
22
|
-
# Returns an attachable_sgid for embedding the file in rich text content.
|
|
23
|
-
#
|
|
24
|
-
# @param filename [String] filename for the uploaded file
|
|
25
|
-
# @param content_type [String] MIME content type (e.g., "image/png", "application/pdf")
|
|
26
|
-
# @param data [String] file data
|
|
27
|
-
# @return [Hash] attachment response with attachable_sgid
|
|
28
|
-
def create(filename:, content_type:, data:)
|
|
29
|
-
http_post_raw("/attachments.json?name=#{URI.encode_www_form_component(filename)}", body: data, content_type: content_type).json
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
end
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Basecamp
|
|
4
|
-
module Services
|
|
5
|
-
# Service for campfire (chat) operations.
|
|
6
|
-
#
|
|
7
|
-
# Campfires are real-time chat rooms within Basecamp projects.
|
|
8
|
-
# They contain lines (messages) and can have chatbot integrations.
|
|
9
|
-
#
|
|
10
|
-
# @example List all campfires
|
|
11
|
-
# account.campfires.list.each do |campfire|
|
|
12
|
-
# puts campfire["title"]
|
|
13
|
-
# end
|
|
14
|
-
#
|
|
15
|
-
# @example Send a message
|
|
16
|
-
# line = account.campfires.create_line(
|
|
17
|
-
# project_id: 123,
|
|
18
|
-
# campfire_id: 456,
|
|
19
|
-
# content: "Hello team!"
|
|
20
|
-
# )
|
|
21
|
-
class CampfiresService < BaseService
|
|
22
|
-
# Lists all campfires across the account.
|
|
23
|
-
#
|
|
24
|
-
# @return [Enumerator<Hash>] campfires
|
|
25
|
-
def list
|
|
26
|
-
paginate("/chats.json")
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
# Gets a specific campfire.
|
|
30
|
-
#
|
|
31
|
-
# @param project_id [Integer, String] project (bucket) ID
|
|
32
|
-
# @param campfire_id [Integer, String] campfire ID
|
|
33
|
-
# @return [Hash] campfire data
|
|
34
|
-
def get(project_id:, campfire_id:)
|
|
35
|
-
http_get(bucket_path(project_id, "/chats/#{campfire_id}.json")).json
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
# Lists all lines (messages) in a campfire.
|
|
39
|
-
#
|
|
40
|
-
# @param project_id [Integer, String] project (bucket) ID
|
|
41
|
-
# @param campfire_id [Integer, String] campfire ID
|
|
42
|
-
# @return [Enumerator<Hash>] campfire lines
|
|
43
|
-
def list_lines(project_id:, campfire_id:)
|
|
44
|
-
paginate(bucket_path(project_id, "/chats/#{campfire_id}/lines.json"))
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
# Gets a specific line (message) from a campfire.
|
|
48
|
-
#
|
|
49
|
-
# @param project_id [Integer, String] project (bucket) ID
|
|
50
|
-
# @param campfire_id [Integer, String] campfire ID
|
|
51
|
-
# @param line_id [Integer, String] line ID
|
|
52
|
-
# @return [Hash] campfire line
|
|
53
|
-
def get_line(project_id:, campfire_id:, line_id:)
|
|
54
|
-
http_get(bucket_path(project_id, "/chats/#{campfire_id}/lines/#{line_id}.json")).json
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
# Creates a new line (message) in a campfire.
|
|
58
|
-
#
|
|
59
|
-
# @param project_id [Integer, String] project (bucket) ID
|
|
60
|
-
# @param campfire_id [Integer, String] campfire ID
|
|
61
|
-
# @param content [String] plain text message content
|
|
62
|
-
# @return [Hash] created line
|
|
63
|
-
def create_line(project_id:, campfire_id:, content:)
|
|
64
|
-
body = { content: content }
|
|
65
|
-
http_post(bucket_path(project_id, "/chats/#{campfire_id}/lines.json"), body: body).json
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
# Deletes a line (message) from a campfire.
|
|
69
|
-
#
|
|
70
|
-
# @param project_id [Integer, String] project (bucket) ID
|
|
71
|
-
# @param campfire_id [Integer, String] campfire ID
|
|
72
|
-
# @param line_id [Integer, String] line ID
|
|
73
|
-
# @return [void]
|
|
74
|
-
def delete_line(project_id:, campfire_id:, line_id:)
|
|
75
|
-
http_delete(bucket_path(project_id, "/chats/#{campfire_id}/lines/#{line_id}.json"))
|
|
76
|
-
nil
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
# Lists all chatbots for a campfire.
|
|
80
|
-
#
|
|
81
|
-
# @param project_id [Integer, String] project (bucket) ID
|
|
82
|
-
# @param campfire_id [Integer, String] campfire ID
|
|
83
|
-
# @return [Enumerator<Hash>] chatbots
|
|
84
|
-
def list_chatbots(project_id:, campfire_id:)
|
|
85
|
-
paginate(bucket_path(project_id, "/chats/#{campfire_id}/integrations.json"))
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
# Gets a specific chatbot.
|
|
89
|
-
#
|
|
90
|
-
# @param project_id [Integer, String] project (bucket) ID
|
|
91
|
-
# @param campfire_id [Integer, String] campfire ID
|
|
92
|
-
# @param chatbot_id [Integer, String] chatbot ID
|
|
93
|
-
# @return [Hash] chatbot data
|
|
94
|
-
def get_chatbot(project_id:, campfire_id:, chatbot_id:)
|
|
95
|
-
http_get(bucket_path(project_id, "/chats/#{campfire_id}/integrations/#{chatbot_id}.json")).json
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
# Creates a new chatbot for a campfire.
|
|
99
|
-
#
|
|
100
|
-
# @param project_id [Integer, String] project (bucket) ID
|
|
101
|
-
# @param campfire_id [Integer, String] campfire ID
|
|
102
|
-
# @param service_name [String] chatbot name (no spaces, emoji, or non-word characters)
|
|
103
|
-
# @param command_url [String, nil] HTTPS URL for bot callbacks
|
|
104
|
-
# @return [Hash] created chatbot with lines_url for posting
|
|
105
|
-
def create_chatbot(project_id:, campfire_id:, service_name:, command_url: nil)
|
|
106
|
-
body = compact_params(
|
|
107
|
-
service_name: service_name,
|
|
108
|
-
command_url: command_url
|
|
109
|
-
)
|
|
110
|
-
http_post(bucket_path(project_id, "/chats/#{campfire_id}/integrations.json"), body: body).json
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
# Updates an existing chatbot.
|
|
114
|
-
#
|
|
115
|
-
# @param project_id [Integer, String] project (bucket) ID
|
|
116
|
-
# @param campfire_id [Integer, String] campfire ID
|
|
117
|
-
# @param chatbot_id [Integer, String] chatbot ID
|
|
118
|
-
# @param service_name [String] new chatbot name
|
|
119
|
-
# @param command_url [String, nil] new callback URL
|
|
120
|
-
# @return [Hash] updated chatbot
|
|
121
|
-
def update_chatbot(project_id:, campfire_id:, chatbot_id:, service_name:, command_url: nil)
|
|
122
|
-
body = compact_params(
|
|
123
|
-
service_name: service_name,
|
|
124
|
-
command_url: command_url
|
|
125
|
-
)
|
|
126
|
-
http_put(bucket_path(project_id, "/chats/#{campfire_id}/integrations/#{chatbot_id}.json"), body: body).json
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
# Deletes a chatbot.
|
|
130
|
-
#
|
|
131
|
-
# @param project_id [Integer, String] project (bucket) ID
|
|
132
|
-
# @param campfire_id [Integer, String] campfire ID
|
|
133
|
-
# @param chatbot_id [Integer, String] chatbot ID
|
|
134
|
-
# @return [void]
|
|
135
|
-
def delete_chatbot(project_id:, campfire_id:, chatbot_id:)
|
|
136
|
-
http_delete(bucket_path(project_id, "/chats/#{campfire_id}/integrations/#{chatbot_id}.json"))
|
|
137
|
-
nil
|
|
138
|
-
end
|
|
139
|
-
end
|
|
140
|
-
end
|
|
141
|
-
end
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Basecamp
|
|
4
|
-
module Services
|
|
5
|
-
# Service for card column operations.
|
|
6
|
-
#
|
|
7
|
-
# Columns are lists within a card table that contain cards.
|
|
8
|
-
#
|
|
9
|
-
# @example Create a column
|
|
10
|
-
# column = account.card_columns.create(
|
|
11
|
-
# project_id: 123,
|
|
12
|
-
# card_table_id: 456,
|
|
13
|
-
# title: "In Review"
|
|
14
|
-
# )
|
|
15
|
-
#
|
|
16
|
-
# @example Set column color
|
|
17
|
-
# account.card_columns.set_color(project_id: 123, column_id: 456, color: "blue")
|
|
18
|
-
class CardColumnsService < BaseService
|
|
19
|
-
# Gets a column by ID.
|
|
20
|
-
#
|
|
21
|
-
# @param project_id [Integer, String] project (bucket) ID
|
|
22
|
-
# @param column_id [Integer, String] column ID
|
|
23
|
-
# @return [Hash] column data
|
|
24
|
-
def get(project_id:, column_id:)
|
|
25
|
-
http_get(bucket_path(project_id, "/card_tables/columns/#{column_id}.json")).json
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
# Creates a new column in a card table.
|
|
29
|
-
#
|
|
30
|
-
# @param project_id [Integer, String] project (bucket) ID
|
|
31
|
-
# @param card_table_id [Integer, String] card table ID
|
|
32
|
-
# @param title [String] column title
|
|
33
|
-
# @param description [String, nil] column description
|
|
34
|
-
# @return [Hash] created column
|
|
35
|
-
def create(project_id:, card_table_id:, title:, description: nil)
|
|
36
|
-
body = compact_params(
|
|
37
|
-
title: title,
|
|
38
|
-
description: description
|
|
39
|
-
)
|
|
40
|
-
http_post(bucket_path(project_id, "/card_tables/#{card_table_id}/columns.json"), body: body).json
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
# Updates an existing column.
|
|
44
|
-
#
|
|
45
|
-
# @param project_id [Integer, String] project (bucket) ID
|
|
46
|
-
# @param column_id [Integer, String] column ID
|
|
47
|
-
# @param title [String, nil] new title
|
|
48
|
-
# @param description [String, nil] new description
|
|
49
|
-
# @return [Hash] updated column
|
|
50
|
-
def update(project_id:, column_id:, title: nil, description: nil)
|
|
51
|
-
body = compact_params(
|
|
52
|
-
title: title,
|
|
53
|
-
description: description
|
|
54
|
-
)
|
|
55
|
-
http_put(bucket_path(project_id, "/card_tables/columns/#{column_id}.json"), body: body).json
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
# Moves a column within a card table.
|
|
59
|
-
#
|
|
60
|
-
# @param project_id [Integer, String] project (bucket) ID
|
|
61
|
-
# @param card_table_id [Integer, String] card table ID
|
|
62
|
-
# @param source_id [Integer, String] column ID to move
|
|
63
|
-
# @param target_id [Integer, String] column ID to move relative to
|
|
64
|
-
# @param position [Integer, nil] position relative to target
|
|
65
|
-
# @return [void]
|
|
66
|
-
def move(project_id:, card_table_id:, source_id:, target_id:, position: nil)
|
|
67
|
-
body = compact_params(
|
|
68
|
-
source_id: source_id,
|
|
69
|
-
target_id: target_id,
|
|
70
|
-
position: position
|
|
71
|
-
)
|
|
72
|
-
http_post(bucket_path(project_id, "/card_tables/#{card_table_id}/moves.json"), body: body)
|
|
73
|
-
nil
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
# Sets the color of a column.
|
|
77
|
-
#
|
|
78
|
-
# @param project_id [Integer, String] project (bucket) ID
|
|
79
|
-
# @param column_id [Integer, String] column ID
|
|
80
|
-
# @param color [String] color name (white, red, orange, yellow, green, blue, aqua, purple, gray, pink, brown)
|
|
81
|
-
# @return [Hash] updated column
|
|
82
|
-
def set_color(project_id:, column_id:, color:)
|
|
83
|
-
http_put(bucket_path(project_id, "/card_tables/columns/#{column_id}/color.json"),
|
|
84
|
-
body: { color: color }).json
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
# Adds an on-hold section to a column.
|
|
88
|
-
#
|
|
89
|
-
# @param project_id [Integer, String] project (bucket) ID
|
|
90
|
-
# @param column_id [Integer, String] column ID
|
|
91
|
-
# @return [Hash] updated column
|
|
92
|
-
def enable_on_hold(project_id:, column_id:)
|
|
93
|
-
http_post(bucket_path(project_id, "/card_tables/columns/#{column_id}/on_hold.json")).json
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
# Removes the on-hold section from a column.
|
|
97
|
-
#
|
|
98
|
-
# @param project_id [Integer, String] project (bucket) ID
|
|
99
|
-
# @param column_id [Integer, String] column ID
|
|
100
|
-
# @return [Hash] updated column
|
|
101
|
-
def disable_on_hold(project_id:, column_id:)
|
|
102
|
-
http_delete(bucket_path(project_id, "/card_tables/columns/#{column_id}/on_hold.json")).json
|
|
103
|
-
end
|
|
104
|
-
end
|
|
105
|
-
end
|
|
106
|
-
end
|