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
574 575 576 |
# File 'lib/basecamp/client.rb', line 574 def account service(:account) { Services::AccountService.new(self) } end |
#attachments ⇒ Services::AttachmentsService
419 420 421 |
# File 'lib/basecamp/client.rb', line 419 def service(:attachments) { Services::AttachmentsService.new(self) } end |
#automation ⇒ Services::AutomationService
489 490 491 |
# File 'lib/basecamp/client.rb', line 489 def automation service(:automation) { Services::AutomationService.new(self) } end |
#bookmarks ⇒ Services::BookmarksService
504 505 506 |
# File 'lib/basecamp/client.rb', line 504 def bookmarks service(:bookmarks) { Services::BookmarksService.new(self) } end |
#boosts ⇒ Services::BoostsService
569 570 571 |
# File 'lib/basecamp/client.rb', line 569 def boosts service(:boosts) { Services::BoostsService.new(self) } end |
#calendars ⇒ Services::CalendarsService
519 520 521 |
# File 'lib/basecamp/client.rb', line 519 def calendars service(:calendars) { Services::CalendarsService.new(self) } end |
#campfires ⇒ Services::CampfiresService
379 380 381 |
# File 'lib/basecamp/client.rb', line 379 def campfires service(:campfires) { Services::CampfiresService.new(self) } end |
#card_columns ⇒ Services::CardColumnsService
444 445 446 |
# File 'lib/basecamp/client.rb', line 444 def card_columns service(:card_columns) { Services::CardColumnsService.new(self) } end |
#card_steps ⇒ Services::CardStepsService
449 450 451 |
# File 'lib/basecamp/client.rb', line 449 def card_steps service(:card_steps) { Services::CardStepsService.new(self) } end |
#card_tables ⇒ Services::CardTablesService
434 435 436 |
# File 'lib/basecamp/client.rb', line 434 def card_tables service(:card_tables) { Services::CardTablesService.new(self) } end |
#cards ⇒ Services::CardsService
439 440 441 |
# File 'lib/basecamp/client.rb', line 439 def cards service(:cards) { Services::CardsService.new(self) } end |
#checkins ⇒ Services::CheckinsService
424 425 426 |
# File 'lib/basecamp/client.rb', line 424 def checkins service(:checkins) { Services::CheckinsService.new(self) } end |
#client_approvals ⇒ Services::ClientApprovalsService
469 470 471 |
# File 'lib/basecamp/client.rb', line 469 def client_approvals service(:client_approvals) { Services::ClientApprovalsService.new(self) } end |
#client_correspondences ⇒ Services::ClientCorrespondencesService
474 475 476 |
# File 'lib/basecamp/client.rb', line 474 def client_correspondences service(:client_correspondences) { Services::ClientCorrespondencesService.new(self) } end |
#client_replies ⇒ Services::ClientRepliesService
479 480 481 |
# File 'lib/basecamp/client.rb', line 479 def client_replies service(:client_replies) { Services::ClientRepliesService.new(self) } end |
#client_visibility ⇒ Services::ClientVisibilityService
559 560 561 |
# File 'lib/basecamp/client.rb', line 559 def client_visibility service(:client_visibility) { Services::ClientVisibilityService.new(self) } end |
#cloud_files ⇒ Services::CloudFilesService
404 405 406 |
# File 'lib/basecamp/client.rb', line 404 def cloud_files service(:cloud_files) { Services::CloudFilesService.new(self) } end |
#comments ⇒ Services::CommentsService
359 360 361 |
# File 'lib/basecamp/client.rb', line 359 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
399 400 401 |
# File 'lib/basecamp/client.rb', line 399 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.
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 316 317 318 319 320 321 322 323 324 |
# File 'lib/basecamp/client.rb', line 250 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
514 515 516 |
# File 'lib/basecamp/client.rb', line 514 def drafts service(:drafts) { Services::DraftsService.new(self) } end |
#events ⇒ Services::EventsService
464 465 466 |
# File 'lib/basecamp/client.rb', line 464 def events service(:events) { Services::EventsService.new(self) } end |
#everything ⇒ Services::EverythingService
549 550 551 |
# File 'lib/basecamp/client.rb', line 549 def everything service(:everything) { Services::EverythingService.new(self) } end |
#folders ⇒ Services::FoldersService
509 510 511 |
# File 'lib/basecamp/client.rb', line 509 def folders service(:folders) { Services::FoldersService.new(self) } end |
#forwards ⇒ Services::ForwardsService
429 430 431 |
# File 'lib/basecamp/client.rb', line 429 def forwards service(:forwards) { Services::ForwardsService.new(self) } end |
#gauges ⇒ Services::GaugesService
579 580 581 |
# File 'lib/basecamp/client.rb', line 579 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
409 410 411 |
# File 'lib/basecamp/client.rb', line 409 def google_documents service(:google_documents) { Services::GoogleDocumentsService.new(self) } end |
#hill_charts ⇒ Services::HillChartsService
344 345 346 |
# File 'lib/basecamp/client.rb', line 344 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
484 485 486 |
# File 'lib/basecamp/client.rb', line 484 def lineup service(:lineup) { Services::LineupService.new(self) } end |
#message_boards ⇒ Services::MessageBoardsService
369 370 371 |
# File 'lib/basecamp/client.rb', line 369 def service(:message_boards) { Services::MessageBoardsService.new(self) } end |
#message_types ⇒ Services::MessageTypesService
494 495 496 |
# File 'lib/basecamp/client.rb', line 494 def service(:message_types) { Services::MessageTypesService.new(self) } end |
#messages ⇒ Services::MessagesService
364 365 366 |
# File 'lib/basecamp/client.rb', line 364 def service(:messages) { Services::MessagesService.new(self) } end |
#my_assignments ⇒ Services::MyAssignmentsService
584 585 586 |
# File 'lib/basecamp/client.rb', line 584 def my_assignments service(:my_assignments) { Services::MyAssignmentsService.new(self) } end |
#my_notes ⇒ Services::MyNotesService
524 525 526 |
# File 'lib/basecamp/client.rb', line 524 def my_notes service(:my_notes) { Services::MyNotesService.new(self) } end |
#my_notifications ⇒ Services::MyNotificationsService
589 590 591 |
# File 'lib/basecamp/client.rb', line 589 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
354 355 356 |
# File 'lib/basecamp/client.rb', line 354 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
329 330 331 |
# File 'lib/basecamp/client.rb', line 329 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
394 395 396 |
# File 'lib/basecamp/client.rb', line 394 def recordings service(:recordings) { Services::RecordingsService.new(self) } end |
#reports ⇒ Services::ReportsService
539 540 541 |
# File 'lib/basecamp/client.rb', line 539 def reports service(:reports) { Services::ReportsService.new(self) } end |
#schedules ⇒ Services::SchedulesService
384 385 386 |
# File 'lib/basecamp/client.rb', line 384 def schedules service(:schedules) { Services::SchedulesService.new(self) } end |
#search ⇒ Services::SearchService
534 535 536 |
# File 'lib/basecamp/client.rb', line 534 def search service(:search) { Services::SearchService.new(self) } end |
#subscriptions ⇒ Services::SubscriptionsService
529 530 531 |
# File 'lib/basecamp/client.rb', line 529 def subscriptions service(:subscriptions) { Services::SubscriptionsService.new(self) } end |
#templates ⇒ Services::TemplatesService
459 460 461 |
# File 'lib/basecamp/client.rb', line 459 def templates service(:templates) { Services::TemplatesService.new(self) } end |
#timeline ⇒ Services::TimelineService
544 545 546 |
# File 'lib/basecamp/client.rb', line 544 def timeline service(:timeline) { Services::TimelineService.new(self) } end |
#timesheets ⇒ Services::TimesheetsService
554 555 556 |
# File 'lib/basecamp/client.rb', line 554 def timesheets service(:timesheets) { Services::TimesheetsService.new(self) } end |
#todolist_groups ⇒ Services::TodolistGroupsService
564 565 566 |
# File 'lib/basecamp/client.rb', line 564 def todolist_groups service(:todolist_groups) { Services::TodolistGroupsService.new(self) } end |
#todolists ⇒ Services::TodolistsService
349 350 351 |
# File 'lib/basecamp/client.rb', line 349 def todolists service(:todolists) { Services::TodolistsService.new(self) } end |
#todos ⇒ Services::TodosService
334 335 336 |
# File 'lib/basecamp/client.rb', line 334 def todos service(:todos) { Services::TodosService.new(self) } end |
#todosets ⇒ Services::TodosetsService
339 340 341 |
# File 'lib/basecamp/client.rb', line 339 def todosets service(:todosets) { Services::TodosetsService.new(self) } end |
#tools ⇒ Services::ToolsService
499 500 501 |
# File 'lib/basecamp/client.rb', line 499 def tools service(:tools) { Services::ToolsService.new(self) } end |
#uploads ⇒ Services::UploadsService
414 415 416 |
# File 'lib/basecamp/client.rb', line 414 def uploads service(:uploads) { Services::UploadsService.new(self) } end |
#vaults ⇒ Services::VaultsService
389 390 391 |
# File 'lib/basecamp/client.rb', line 389 def vaults service(:vaults) { Services::VaultsService.new(self) } end |
#webhooks ⇒ Services::WebhooksService
374 375 376 |
# File 'lib/basecamp/client.rb', line 374 def webhooks service(:webhooks) { Services::WebhooksService.new(self) } end |
#wormholes ⇒ Services::WormholesService
454 455 456 |
# File 'lib/basecamp/client.rb', line 454 def wormholes service(:wormholes) { Services::WormholesService.new(self) } end |