Class: Basecamp::AccountClient
- Inherits:
-
Object
- Object
- Basecamp::AccountClient
- Defined in:
- lib/basecamp/client.rb
Overview
HTTP client bound to a specific Basecamp account.
Create an AccountClient using Client#for_account. All API operations that require an account context use this class.
Instance Attribute Summary collapse
-
#account_id ⇒ String
readonly
The account ID this client is bound to.
Services collapse
- #account ⇒ Services::AccountService
- #attachments ⇒ Services::AttachmentsService
- #automation ⇒ Services::AutomationService
- #boosts ⇒ Services::BoostsService
- #campfires ⇒ Services::CampfiresService
- #card_columns ⇒ Services::CardColumnsService
- #card_steps ⇒ Services::CardStepsService
- #card_tables ⇒ Services::CardTablesService
- #cards ⇒ Services::CardsService
- #checkins ⇒ Services::CheckinsService
- #client_approvals ⇒ Services::ClientApprovalsService
- #client_correspondences ⇒ Services::ClientCorrespondencesService
- #client_replies ⇒ Services::ClientRepliesService
- #client_visibility ⇒ Services::ClientVisibilityService
- #comments ⇒ Services::CommentsService
- #documents ⇒ Services::DocumentsService
- #events ⇒ Services::EventsService
- #forwards ⇒ Services::ForwardsService
- #gauges ⇒ Services::GaugesService
- #hill_charts ⇒ Services::HillChartsService
- #lineup ⇒ Services::LineupService
- #message_boards ⇒ Services::MessageBoardsService
- #message_types ⇒ Services::MessageTypesService
- #messages ⇒ Services::MessagesService
- #my_assignments ⇒ Services::MyAssignmentsService
- #my_notifications ⇒ Services::MyNotificationsService
- #people ⇒ Services::PeopleService
- #projects ⇒ Services::ProjectsService
- #recordings ⇒ Services::RecordingsService
- #reports ⇒ Services::ReportsService
- #schedules ⇒ Services::SchedulesService
- #search ⇒ Services::SearchService
- #subscriptions ⇒ Services::SubscriptionsService
- #templates ⇒ Services::TemplatesService
- #timeline ⇒ Services::TimelineService
- #timesheets ⇒ Services::TimesheetsService
- #todolist_groups ⇒ Services::TodolistGroupsService
- #todolists ⇒ Services::TodolistsService
- #todos ⇒ Services::TodosService
- #todosets ⇒ Services::TodosetsService
- #tools ⇒ Services::ToolsService
- #uploads ⇒ Services::UploadsService
- #vaults ⇒ Services::VaultsService
- #webhooks ⇒ Services::WebhooksService
- #wormholes ⇒ Services::WormholesService
Instance Method Summary collapse
-
#config ⇒ Config
Client configuration.
-
#delete(path) ⇒ Response
Performs a DELETE request scoped to this account.
-
#download_url(raw_url) ⇒ DownloadResult
Downloads file content from any API-routable download URL.
-
#get(path, params: {}) ⇒ Response
Performs a GET request scoped to this account.
-
#hooks ⇒ Hooks
private
The observability hooks.
-
#http ⇒ Http
private
The HTTP client.
-
#initialize(parent:, account_id:) ⇒ AccountClient
constructor
private
A new instance of AccountClient.
-
#paginate(path, params: {}) {|Hash| ... } ⇒ Enumerator
Fetches all pages of a paginated resource.
-
#paginate_key(path, key:, params: {}) {|Hash| ... } ⇒ Enumerator
Fetches all pages of a paginated resource, extracting items from a key.
-
#paginate_wrapped(path, key:, params: {}) ⇒ Hash
Fetches a wrapped paginated resource, returning wrapper fields + lazy paginated items.
-
#post(path, body: nil) ⇒ Response
Performs a POST request scoped to this account.
-
#post_raw(path, body:, content_type:) ⇒ Response
Performs a POST request with raw binary data scoped to this account.
-
#put(path, body: nil) ⇒ Response
Performs a PUT request scoped to this account.
-
#put_raw(path, body:, content_type:) ⇒ Response
Performs a PUT request with raw binary data scoped to this account.
Constructor Details
#initialize(parent:, account_id:) ⇒ AccountClient
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns a new instance of AccountClient.
127 128 129 130 131 132 |
# File 'lib/basecamp/client.rb', line 127 def initialize(parent:, account_id:) @parent = parent @account_id = account_id @services = {} @mutex = Mutex.new end |
Instance Attribute Details
#account_id ⇒ String (readonly)
Returns the account ID this client is bound to.
122 123 124 |
# File 'lib/basecamp/client.rb', line 122 def account_id @account_id end |
Instance Method Details
#account ⇒ Services::AccountService
525 526 527 |
# File 'lib/basecamp/client.rb', line 525 def account service(:account) { Services::AccountService.new(self) } end |
#attachments ⇒ Services::AttachmentsService
400 401 402 |
# File 'lib/basecamp/client.rb', line 400 def service(:attachments) { Services::AttachmentsService.new(self) } end |
#automation ⇒ Services::AutomationService
470 471 472 |
# File 'lib/basecamp/client.rb', line 470 def automation service(:automation) { Services::AutomationService.new(self) } end |
#boosts ⇒ Services::BoostsService
520 521 522 |
# File 'lib/basecamp/client.rb', line 520 def boosts service(:boosts) { Services::BoostsService.new(self) } end |
#campfires ⇒ Services::CampfiresService
370 371 372 |
# File 'lib/basecamp/client.rb', line 370 def campfires service(:campfires) { Services::CampfiresService.new(self) } end |
#card_columns ⇒ Services::CardColumnsService
425 426 427 |
# File 'lib/basecamp/client.rb', line 425 def card_columns service(:card_columns) { Services::CardColumnsService.new(self) } end |
#card_steps ⇒ Services::CardStepsService
430 431 432 |
# File 'lib/basecamp/client.rb', line 430 def card_steps service(:card_steps) { Services::CardStepsService.new(self) } end |
#card_tables ⇒ Services::CardTablesService
415 416 417 |
# File 'lib/basecamp/client.rb', line 415 def card_tables service(:card_tables) { Services::CardTablesService.new(self) } end |
#cards ⇒ Services::CardsService
420 421 422 |
# File 'lib/basecamp/client.rb', line 420 def cards service(:cards) { Services::CardsService.new(self) } end |
#checkins ⇒ Services::CheckinsService
405 406 407 |
# File 'lib/basecamp/client.rb', line 405 def checkins service(:checkins) { Services::CheckinsService.new(self) } end |
#client_approvals ⇒ Services::ClientApprovalsService
450 451 452 |
# File 'lib/basecamp/client.rb', line 450 def client_approvals service(:client_approvals) { Services::ClientApprovalsService.new(self) } end |
#client_correspondences ⇒ Services::ClientCorrespondencesService
455 456 457 |
# File 'lib/basecamp/client.rb', line 455 def client_correspondences service(:client_correspondences) { Services::ClientCorrespondencesService.new(self) } end |
#client_replies ⇒ Services::ClientRepliesService
460 461 462 |
# File 'lib/basecamp/client.rb', line 460 def client_replies service(:client_replies) { Services::ClientRepliesService.new(self) } end |
#client_visibility ⇒ Services::ClientVisibilityService
510 511 512 |
# File 'lib/basecamp/client.rb', line 510 def client_visibility service(:client_visibility) { Services::ClientVisibilityService.new(self) } end |
#comments ⇒ Services::CommentsService
350 351 352 |
# File 'lib/basecamp/client.rb', line 350 def comments service(:comments) { Services::CommentsService.new(self) } end |
#config ⇒ Config
Returns client configuration.
135 136 137 |
# File 'lib/basecamp/client.rb', line 135 def config @parent.config end |
#delete(path) ⇒ Response
Performs a DELETE request scoped to this account.
178 179 180 |
# File 'lib/basecamp/client.rb', line 178 def delete(path) @parent.http.delete(account_path(path)) end |
#documents ⇒ Services::DocumentsService
390 391 392 |
# File 'lib/basecamp/client.rb', line 390 def documents service(:documents) { Services::DocumentsService.new(self) } end |
#download_url(raw_url) ⇒ DownloadResult
Downloads file content from any API-routable download URL.
Handles the full download flow: URL rewriting to the configured API host, authenticated first hop (which typically 302s to a signed download URL), and unauthenticated second hop to fetch the actual file content.
242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 |
# File 'lib/basecamp/client.rb', line 242 def download_url(raw_url) # Validation raise UsageError.new("download URL is required") if raw_url.nil? || raw_url.to_s.empty? begin parsed = URI.parse(raw_url) rescue URI::InvalidURIError raise UsageError.new("download URL must be an absolute URL") end raise UsageError.new("download URL must be an absolute URL") unless parsed.is_a?(URI::HTTP) # Operation hooks op = OperationInfo.new( service: "Account", operation: "DownloadURL", resource_type: "download", is_mutation: false ) start = Process.clock_gettime(Process::CLOCK_MONOTONIC) safe_hook { hooks.on_operation_start(op) } begin # URL rewriting: replace scheme+host with config.base_url origin, preserve path+query+fragment base = URI.parse(config.base_url) rewritten = parsed.dup rewritten.scheme = base.scheme rewritten.host = base.host rewritten.port = base.port rewritten_url = rewritten.to_s # Hop 1: Authenticated API request (no retry, captures redirect) response = http.get_no_retry(rewritten_url) result = case response.status when 301, 302, 303, 307, 308 # Redirect — extract Location, proceed to hop 2 location = response.headers["Location"] || response.headers["location"] raise ApiError.new("redirect #{response.status} with no Location header") if location.nil? || location.empty? # Resolve relative Location against the rewritten API URL resolved_url = Security.resolve_url(rewritten_url, location) # Hop 2: fetch from signed URL (no auth, no hooks) signed_response = fetch_signed_download(resolved_url) DownloadResult.new( body: signed_response.body, content_type: signed_response["Content-Type"] || "", content_length: parse_content_length(signed_response["Content-Length"]), filename: Basecamp.filename_from_url(raw_url) ) when 200..299 # Direct download — no second hop DownloadResult.new( body: response.body, content_type: response.headers["Content-Type"] || response.headers["content-type"] || "", content_length: parse_content_length(response.headers["Content-Length"] || response.headers["content-length"]), filename: Basecamp.filename_from_url(raw_url) ) else # This shouldn't happen because Faraday's raise_error middleware # handles 4xx/5xx, but handle it defensively raise Basecamp.error_from_response(response.status, response.body) end rescue => e duration = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000).round safe_hook { hooks.on_operation_end(op, OperationResult.new(duration_ms: duration, error: e)) } raise else duration = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000).round safe_hook { hooks.on_operation_end(op, OperationResult.new(duration_ms: duration, error: nil)) } result end end |
#events ⇒ Services::EventsService
445 446 447 |
# File 'lib/basecamp/client.rb', line 445 def events service(:events) { Services::EventsService.new(self) } end |
#forwards ⇒ Services::ForwardsService
410 411 412 |
# File 'lib/basecamp/client.rb', line 410 def forwards service(:forwards) { Services::ForwardsService.new(self) } end |
#gauges ⇒ Services::GaugesService
530 531 532 |
# File 'lib/basecamp/client.rb', line 530 def gauges service(:gauges) { Services::GaugesService.new(self) } end |
#get(path, params: {}) ⇒ Response
Performs a GET request scoped to this account.
155 156 157 |
# File 'lib/basecamp/client.rb', line 155 def get(path, params: {}) @parent.http.get(account_path(path), params: params) end |
#hill_charts ⇒ Services::HillChartsService
335 336 337 |
# File 'lib/basecamp/client.rb', line 335 def hill_charts service(:hill_charts) { Services::HillChartsService.new(self) } end |
#hooks ⇒ Hooks
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns the observability hooks.
147 148 149 |
# File 'lib/basecamp/client.rb', line 147 def hooks @parent.hooks end |
#http ⇒ Http
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns the HTTP client.
141 142 143 |
# File 'lib/basecamp/client.rb', line 141 def http @parent.http end |
#lineup ⇒ Services::LineupService
465 466 467 |
# File 'lib/basecamp/client.rb', line 465 def lineup service(:lineup) { Services::LineupService.new(self) } end |
#message_boards ⇒ Services::MessageBoardsService
360 361 362 |
# File 'lib/basecamp/client.rb', line 360 def service(:message_boards) { Services::MessageBoardsService.new(self) } end |
#message_types ⇒ Services::MessageTypesService
475 476 477 |
# File 'lib/basecamp/client.rb', line 475 def service(:message_types) { Services::MessageTypesService.new(self) } end |
#messages ⇒ Services::MessagesService
355 356 357 |
# File 'lib/basecamp/client.rb', line 355 def service(:messages) { Services::MessagesService.new(self) } end |
#my_assignments ⇒ Services::MyAssignmentsService
535 536 537 |
# File 'lib/basecamp/client.rb', line 535 def my_assignments service(:my_assignments) { Services::MyAssignmentsService.new(self) } end |
#my_notifications ⇒ Services::MyNotificationsService
540 541 542 |
# File 'lib/basecamp/client.rb', line 540 def my_notifications service(:my_notifications) { Services::MyNotificationsService.new(self) } end |
#paginate(path, params: {}) {|Hash| ... } ⇒ Enumerator
Fetches all pages of a paginated resource.
207 208 209 |
# File 'lib/basecamp/client.rb', line 207 def paginate(path, params: {}, &) @parent.http.paginate(account_path(path), params: params, &) end |
#paginate_key(path, key:, params: {}) {|Hash| ... } ⇒ Enumerator
Fetches all pages of a paginated resource, extracting items from a key. Use this for endpoints that return objects like { "events": [...] }.
218 219 220 |
# File 'lib/basecamp/client.rb', line 218 def paginate_key(path, key:, params: {}, &) @parent.http.paginate_key(account_path(path), key: key, params: params, &) end |
#paginate_wrapped(path, key:, params: {}) ⇒ Hash
Fetches a wrapped paginated resource, returning wrapper fields + lazy paginated items.
227 228 229 |
# File 'lib/basecamp/client.rb', line 227 def paginate_wrapped(path, key:, params: {}) @parent.http.paginate_wrapped(account_path(path), key: key, params: params) end |
#people ⇒ Services::PeopleService
345 346 347 |
# File 'lib/basecamp/client.rb', line 345 def people service(:people) { Services::PeopleService.new(self) } end |
#post(path, body: nil) ⇒ Response
Performs a POST request scoped to this account.
163 164 165 |
# File 'lib/basecamp/client.rb', line 163 def post(path, body: nil) @parent.http.post(account_path(path), body: body) end |
#post_raw(path, body:, content_type:) ⇒ Response
Performs a POST request with raw binary data scoped to this account. Used for file uploads (attachments).
188 189 190 |
# File 'lib/basecamp/client.rb', line 188 def post_raw(path, body:, content_type:) @parent.http.post_raw(account_path(path), body: body, content_type: content_type) end |
#projects ⇒ Services::ProjectsService
320 321 322 |
# File 'lib/basecamp/client.rb', line 320 def projects service(:projects) { Services::ProjectsService.new(self) } end |
#put(path, body: nil) ⇒ Response
Performs a PUT request scoped to this account.
171 172 173 |
# File 'lib/basecamp/client.rb', line 171 def put(path, body: nil) @parent.http.put(account_path(path), body: body) end |
#put_raw(path, body:, content_type:) ⇒ Response
Performs a PUT request with raw binary data scoped to this account. Used for multipart uploads (e.g., account logo).
198 199 200 |
# File 'lib/basecamp/client.rb', line 198 def put_raw(path, body:, content_type:) @parent.http.put_raw(account_path(path), body: body, content_type: content_type) end |
#recordings ⇒ Services::RecordingsService
385 386 387 |
# File 'lib/basecamp/client.rb', line 385 def recordings service(:recordings) { Services::RecordingsService.new(self) } end |
#reports ⇒ Services::ReportsService
495 496 497 |
# File 'lib/basecamp/client.rb', line 495 def reports service(:reports) { Services::ReportsService.new(self) } end |
#schedules ⇒ Services::SchedulesService
375 376 377 |
# File 'lib/basecamp/client.rb', line 375 def schedules service(:schedules) { Services::SchedulesService.new(self) } end |
#search ⇒ Services::SearchService
490 491 492 |
# File 'lib/basecamp/client.rb', line 490 def search service(:search) { Services::SearchService.new(self) } end |
#subscriptions ⇒ Services::SubscriptionsService
485 486 487 |
# File 'lib/basecamp/client.rb', line 485 def subscriptions service(:subscriptions) { Services::SubscriptionsService.new(self) } end |
#templates ⇒ Services::TemplatesService
440 441 442 |
# File 'lib/basecamp/client.rb', line 440 def templates service(:templates) { Services::TemplatesService.new(self) } end |
#timeline ⇒ Services::TimelineService
500 501 502 |
# File 'lib/basecamp/client.rb', line 500 def timeline service(:timeline) { Services::TimelineService.new(self) } end |
#timesheets ⇒ Services::TimesheetsService
505 506 507 |
# File 'lib/basecamp/client.rb', line 505 def timesheets service(:timesheets) { Services::TimesheetsService.new(self) } end |
#todolist_groups ⇒ Services::TodolistGroupsService
515 516 517 |
# File 'lib/basecamp/client.rb', line 515 def todolist_groups service(:todolist_groups) { Services::TodolistGroupsService.new(self) } end |
#todolists ⇒ Services::TodolistsService
340 341 342 |
# File 'lib/basecamp/client.rb', line 340 def todolists service(:todolists) { Services::TodolistsService.new(self) } end |
#todos ⇒ Services::TodosService
325 326 327 |
# File 'lib/basecamp/client.rb', line 325 def todos service(:todos) { Services::TodosService.new(self) } end |
#todosets ⇒ Services::TodosetsService
330 331 332 |
# File 'lib/basecamp/client.rb', line 330 def todosets service(:todosets) { Services::TodosetsService.new(self) } end |
#tools ⇒ Services::ToolsService
480 481 482 |
# File 'lib/basecamp/client.rb', line 480 def tools service(:tools) { Services::ToolsService.new(self) } end |
#uploads ⇒ Services::UploadsService
395 396 397 |
# File 'lib/basecamp/client.rb', line 395 def uploads service(:uploads) { Services::UploadsService.new(self) } end |
#vaults ⇒ Services::VaultsService
380 381 382 |
# File 'lib/basecamp/client.rb', line 380 def vaults service(:vaults) { Services::VaultsService.new(self) } end |
#webhooks ⇒ Services::WebhooksService
365 366 367 |
# File 'lib/basecamp/client.rb', line 365 def webhooks service(:webhooks) { Services::WebhooksService.new(self) } end |
#wormholes ⇒ Services::WormholesService
435 436 437 |
# File 'lib/basecamp/client.rb', line 435 def wormholes service(:wormholes) { Services::WormholesService.new(self) } end |