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
- #bookmarks ⇒ Services::BookmarksService
- #boosts ⇒ Services::BoostsService
- #calendars ⇒ Services::CalendarsService
- #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
- #cloud_files ⇒ Services::CloudFilesService
- #comments ⇒ Services::CommentsService
- #documents ⇒ Services::DocumentsService
- #drafts ⇒ Services::DraftsService
- #events ⇒ Services::EventsService
- #everything ⇒ Services::EverythingService
- #folders ⇒ Services::FoldersService
- #forwards ⇒ Services::ForwardsService
- #gauges ⇒ Services::GaugesService
- #google_documents ⇒ Services::GoogleDocumentsService
- #hill_charts ⇒ Services::HillChartsService
- #lineup ⇒ Services::LineupService
- #message_boards ⇒ Services::MessageBoardsService
- #message_types ⇒ Services::MessageTypesService
- #messages ⇒ Services::MessagesService
- #my_assignments ⇒ Services::MyAssignmentsService
- #my_notes ⇒ Services::MyNotesService
- #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: {}, operation: nil) ⇒ 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: {}, operation: nil, max_items: nil) {|Hash| ... } ⇒ ListEnumerator
Fetches all pages of a paginated resource.
-
#paginate_key(path, key:, params: {}, operation: nil, max_items: nil) {|Hash| ... } ⇒ ListEnumerator
Fetches all pages of a paginated resource, extracting items from a key.
-
#paginate_wrapped(path, key:, params: {}, operation: nil, max_items: nil) ⇒ 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
576 577 578 |
# File 'lib/basecamp/client.rb', line 576 def account service(:account) { Services::AccountService.new(self) } end |
#attachments ⇒ Services::AttachmentsService
421 422 423 |
# File 'lib/basecamp/client.rb', line 421 def service(:attachments) { Services::AttachmentsService.new(self) } end |
#automation ⇒ Services::AutomationService
491 492 493 |
# File 'lib/basecamp/client.rb', line 491 def automation service(:automation) { Services::AutomationService.new(self) } end |
#bookmarks ⇒ Services::BookmarksService
506 507 508 |
# File 'lib/basecamp/client.rb', line 506 def bookmarks service(:bookmarks) { Services::BookmarksService.new(self) } end |
#boosts ⇒ Services::BoostsService
571 572 573 |
# File 'lib/basecamp/client.rb', line 571 def boosts service(:boosts) { Services::BoostsService.new(self) } end |
#calendars ⇒ Services::CalendarsService
521 522 523 |
# File 'lib/basecamp/client.rb', line 521 def calendars service(:calendars) { Services::CalendarsService.new(self) } end |
#campfires ⇒ Services::CampfiresService
381 382 383 |
# File 'lib/basecamp/client.rb', line 381 def campfires service(:campfires) { Services::CampfiresService.new(self) } end |
#card_columns ⇒ Services::CardColumnsService
446 447 448 |
# File 'lib/basecamp/client.rb', line 446 def card_columns service(:card_columns) { Services::CardColumnsService.new(self) } end |
#card_steps ⇒ Services::CardStepsService
451 452 453 |
# File 'lib/basecamp/client.rb', line 451 def card_steps service(:card_steps) { Services::CardStepsService.new(self) } end |
#card_tables ⇒ Services::CardTablesService
436 437 438 |
# File 'lib/basecamp/client.rb', line 436 def card_tables service(:card_tables) { Services::CardTablesService.new(self) } end |
#cards ⇒ Services::CardsService
441 442 443 |
# File 'lib/basecamp/client.rb', line 441 def cards service(:cards) { Services::CardsService.new(self) } end |
#checkins ⇒ Services::CheckinsService
426 427 428 |
# File 'lib/basecamp/client.rb', line 426 def checkins service(:checkins) { Services::CheckinsService.new(self) } end |
#client_approvals ⇒ Services::ClientApprovalsService
471 472 473 |
# File 'lib/basecamp/client.rb', line 471 def client_approvals service(:client_approvals) { Services::ClientApprovalsService.new(self) } end |
#client_correspondences ⇒ Services::ClientCorrespondencesService
476 477 478 |
# File 'lib/basecamp/client.rb', line 476 def client_correspondences service(:client_correspondences) { Services::ClientCorrespondencesService.new(self) } end |
#client_replies ⇒ Services::ClientRepliesService
481 482 483 |
# File 'lib/basecamp/client.rb', line 481 def client_replies service(:client_replies) { Services::ClientRepliesService.new(self) } end |
#client_visibility ⇒ Services::ClientVisibilityService
561 562 563 |
# File 'lib/basecamp/client.rb', line 561 def client_visibility service(:client_visibility) { Services::ClientVisibilityService.new(self) } end |
#cloud_files ⇒ Services::CloudFilesService
406 407 408 |
# File 'lib/basecamp/client.rb', line 406 def cloud_files service(:cloud_files) { Services::CloudFilesService.new(self) } end |
#comments ⇒ Services::CommentsService
361 362 363 |
# File 'lib/basecamp/client.rb', line 361 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
401 402 403 |
# File 'lib/basecamp/client.rb', line 401 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. Neither hop follows a redirect on its own: hop 1's is the dispatch to hop 2, and a redirect on hop 2 is an error (SPEC §14 "Hop-2 Redirect Policy").
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 316 317 318 319 320 321 322 323 324 325 326 |
# File 'lib/basecamp/client.rb', line 252 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 under the SPEC §14 hop-1 retry # policy (captures redirect; every attempt is authenticated) response = http.get_download(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 |
#drafts ⇒ Services::DraftsService
516 517 518 |
# File 'lib/basecamp/client.rb', line 516 def drafts service(:drafts) { Services::DraftsService.new(self) } end |
#events ⇒ Services::EventsService
466 467 468 |
# File 'lib/basecamp/client.rb', line 466 def events service(:events) { Services::EventsService.new(self) } end |
#everything ⇒ Services::EverythingService
551 552 553 |
# File 'lib/basecamp/client.rb', line 551 def everything service(:everything) { Services::EverythingService.new(self) } end |
#folders ⇒ Services::FoldersService
511 512 513 |
# File 'lib/basecamp/client.rb', line 511 def folders service(:folders) { Services::FoldersService.new(self) } end |
#forwards ⇒ Services::ForwardsService
431 432 433 |
# File 'lib/basecamp/client.rb', line 431 def forwards service(:forwards) { Services::ForwardsService.new(self) } end |
#gauges ⇒ Services::GaugesService
581 582 583 |
# File 'lib/basecamp/client.rb', line 581 def gauges service(:gauges) { Services::GaugesService.new(self) } end |
#get(path, params: {}, operation: nil) ⇒ Response
Performs a GET request scoped to this account.
155 156 157 |
# File 'lib/basecamp/client.rb', line 155 def get(path, params: {}, operation: nil) @parent.http.get(account_path(path), params: params, operation: operation) end |
#google_documents ⇒ Services::GoogleDocumentsService
411 412 413 |
# File 'lib/basecamp/client.rb', line 411 def google_documents service(:google_documents) { Services::GoogleDocumentsService.new(self) } end |
#hill_charts ⇒ Services::HillChartsService
346 347 348 |
# File 'lib/basecamp/client.rb', line 346 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
486 487 488 |
# File 'lib/basecamp/client.rb', line 486 def lineup service(:lineup) { Services::LineupService.new(self) } end |
#message_boards ⇒ Services::MessageBoardsService
371 372 373 |
# File 'lib/basecamp/client.rb', line 371 def service(:message_boards) { Services::MessageBoardsService.new(self) } end |
#message_types ⇒ Services::MessageTypesService
496 497 498 |
# File 'lib/basecamp/client.rb', line 496 def service(:message_types) { Services::MessageTypesService.new(self) } end |
#messages ⇒ Services::MessagesService
366 367 368 |
# File 'lib/basecamp/client.rb', line 366 def service(:messages) { Services::MessagesService.new(self) } end |
#my_assignments ⇒ Services::MyAssignmentsService
586 587 588 |
# File 'lib/basecamp/client.rb', line 586 def my_assignments service(:my_assignments) { Services::MyAssignmentsService.new(self) } end |
#my_notes ⇒ Services::MyNotesService
526 527 528 |
# File 'lib/basecamp/client.rb', line 526 def my_notes service(:my_notes) { Services::MyNotesService.new(self) } end |
#my_notifications ⇒ Services::MyNotificationsService
591 592 593 |
# File 'lib/basecamp/client.rb', line 591 def my_notifications service(:my_notifications) { Services::MyNotificationsService.new(self) } end |
#paginate(path, params: {}, operation: nil, max_items: nil) {|Hash| ... } ⇒ ListEnumerator
Fetches all pages of a paginated resource.
209 210 211 |
# File 'lib/basecamp/client.rb', line 209 def paginate(path, params: {}, operation: nil, max_items: nil, &) @parent.http.paginate(account_path(path), params: params, operation: operation, max_items: max_items, &) end |
#paginate_key(path, key:, params: {}, operation: nil, max_items: nil) {|Hash| ... } ⇒ ListEnumerator
Fetches all pages of a paginated resource, extracting items from a key. Use this for endpoints that return objects like { "events": [...] }.
222 223 224 225 |
# File 'lib/basecamp/client.rb', line 222 def paginate_key(path, key:, params: {}, operation: nil, max_items: nil, &) @parent.http.paginate_key(account_path(path), key: key, params: params, operation: operation, \ max_items: max_items, &) end |
#paginate_wrapped(path, key:, params: {}, operation: nil, max_items: nil) ⇒ Hash
Fetches a wrapped paginated resource, returning wrapper fields + lazy paginated items.
234 235 236 237 |
# File 'lib/basecamp/client.rb', line 234 def paginate_wrapped(path, key:, params: {}, operation: nil, max_items: nil) @parent.http.paginate_wrapped(account_path(path), key: key, params: params, operation: operation, \ max_items: max_items) end |
#people ⇒ Services::PeopleService
356 357 358 |
# File 'lib/basecamp/client.rb', line 356 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
331 332 333 |
# File 'lib/basecamp/client.rb', line 331 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
396 397 398 |
# File 'lib/basecamp/client.rb', line 396 def recordings service(:recordings) { Services::RecordingsService.new(self) } end |
#reports ⇒ Services::ReportsService
541 542 543 |
# File 'lib/basecamp/client.rb', line 541 def reports service(:reports) { Services::ReportsService.new(self) } end |
#schedules ⇒ Services::SchedulesService
386 387 388 |
# File 'lib/basecamp/client.rb', line 386 def schedules service(:schedules) { Services::SchedulesService.new(self) } end |
#search ⇒ Services::SearchService
536 537 538 |
# File 'lib/basecamp/client.rb', line 536 def search service(:search) { Services::SearchService.new(self) } end |
#subscriptions ⇒ Services::SubscriptionsService
531 532 533 |
# File 'lib/basecamp/client.rb', line 531 def subscriptions service(:subscriptions) { Services::SubscriptionsService.new(self) } end |
#templates ⇒ Services::TemplatesService
461 462 463 |
# File 'lib/basecamp/client.rb', line 461 def templates service(:templates) { Services::TemplatesService.new(self) } end |
#timeline ⇒ Services::TimelineService
546 547 548 |
# File 'lib/basecamp/client.rb', line 546 def timeline service(:timeline) { Services::TimelineService.new(self) } end |
#timesheets ⇒ Services::TimesheetsService
556 557 558 |
# File 'lib/basecamp/client.rb', line 556 def timesheets service(:timesheets) { Services::TimesheetsService.new(self) } end |
#todolist_groups ⇒ Services::TodolistGroupsService
566 567 568 |
# File 'lib/basecamp/client.rb', line 566 def todolist_groups service(:todolist_groups) { Services::TodolistGroupsService.new(self) } end |
#todolists ⇒ Services::TodolistsService
351 352 353 |
# File 'lib/basecamp/client.rb', line 351 def todolists service(:todolists) { Services::TodolistsService.new(self) } end |
#todos ⇒ Services::TodosService
336 337 338 |
# File 'lib/basecamp/client.rb', line 336 def todos service(:todos) { Services::TodosService.new(self) } end |
#todosets ⇒ Services::TodosetsService
341 342 343 |
# File 'lib/basecamp/client.rb', line 341 def todosets service(:todosets) { Services::TodosetsService.new(self) } end |
#tools ⇒ Services::ToolsService
501 502 503 |
# File 'lib/basecamp/client.rb', line 501 def tools service(:tools) { Services::ToolsService.new(self) } end |
#uploads ⇒ Services::UploadsService
416 417 418 |
# File 'lib/basecamp/client.rb', line 416 def uploads service(:uploads) { Services::UploadsService.new(self) } end |
#vaults ⇒ Services::VaultsService
391 392 393 |
# File 'lib/basecamp/client.rb', line 391 def vaults service(:vaults) { Services::VaultsService.new(self) } end |
#webhooks ⇒ Services::WebhooksService
376 377 378 |
# File 'lib/basecamp/client.rb', line 376 def webhooks service(:webhooks) { Services::WebhooksService.new(self) } end |
#wormholes ⇒ Services::WormholesService
456 457 458 |
# File 'lib/basecamp/client.rb', line 456 def wormholes service(:wormholes) { Services::WormholesService.new(self) } end |