basecamp-sdk 0.7.3 → 0.9.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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +65 -1
  3. data/basecamp-sdk.gemspec +2 -3
  4. data/lib/basecamp/client.rb +5 -0
  5. data/lib/basecamp/generated/metadata.json +100 -33
  6. data/lib/basecamp/generated/services/base_service.rb +13 -0
  7. data/lib/basecamp/generated/services/campfires_service.rb +15 -3
  8. data/lib/basecamp/generated/services/card_columns_service.rb +26 -23
  9. data/lib/basecamp/generated/services/checkins_service.rb +5 -4
  10. data/lib/basecamp/generated/services/client_approvals_service.rb +1 -1
  11. data/lib/basecamp/generated/services/client_correspondences_service.rb +1 -1
  12. data/lib/basecamp/generated/services/documents_service.rb +3 -2
  13. data/lib/basecamp/generated/services/forwards_service.rb +1 -1
  14. data/lib/basecamp/generated/services/gauges_service.rb +4 -4
  15. data/lib/basecamp/generated/services/messages_service.rb +4 -3
  16. data/lib/basecamp/generated/services/my_assignments_service.rb +1 -1
  17. data/lib/basecamp/generated/services/my_notifications_service.rb +1 -1
  18. data/lib/basecamp/generated/services/people_service.rb +2 -2
  19. data/lib/basecamp/generated/services/projects_service.rb +2 -2
  20. data/lib/basecamp/generated/services/recordings_service.rb +1 -1
  21. data/lib/basecamp/generated/services/reports_service.rb +3 -3
  22. data/lib/basecamp/generated/services/schedules_service.rb +5 -4
  23. data/lib/basecamp/generated/services/search_service.rb +14 -3
  24. data/lib/basecamp/generated/services/templates_service.rb +4 -5
  25. data/lib/basecamp/generated/services/timesheets_service.rb +3 -3
  26. data/lib/basecamp/generated/services/todolists_service.rb +15 -3
  27. data/lib/basecamp/generated/services/todos_service.rb +5 -5
  28. data/lib/basecamp/generated/services/tools_service.rb +7 -6
  29. data/lib/basecamp/generated/services/uploads_service.rb +20 -2
  30. data/lib/basecamp/generated/services/webhooks_service.rb +2 -2
  31. data/lib/basecamp/generated/services/wormholes_service.rb +44 -0
  32. data/lib/basecamp/generated/types.rb +284 -64
  33. data/lib/basecamp/http.rb +86 -15
  34. data/lib/basecamp/oauth/config.rb +20 -5
  35. data/lib/basecamp/oauth/discovery.rb +134 -64
  36. data/lib/basecamp/oauth/discovery_result.rb +33 -0
  37. data/lib/basecamp/oauth/discovery_selection_error.rb +30 -0
  38. data/lib/basecamp/oauth/fetcher.rb +204 -0
  39. data/lib/basecamp/oauth/protected_resource_metadata.rb +22 -0
  40. data/lib/basecamp/oauth/resource.rb +95 -0
  41. data/lib/basecamp/oauth.rb +173 -0
  42. data/lib/basecamp/security.rb +83 -0
  43. data/lib/basecamp/services/authorization_service.rb +45 -0
  44. data/lib/basecamp/services/todos_extensions.rb +121 -0
  45. data/lib/basecamp/version.rb +2 -2
  46. data/lib/basecamp.rb +6 -0
  47. data/scripts/generate-services.rb +61 -13
  48. data/scripts/generate-types.rb +33 -1
  49. metadata +14 -21
  50. data/lib/basecamp/generated/services/authorization_service.rb +0 -47
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d07bf1af7eccbc28f132ef388bf5beb2c2571662e0cfc60b030705c1b8184b15
4
- data.tar.gz: ab40ddc799b5aea73915f596ba38ba556e0c69b67795809d63927aec9c88ce77
3
+ metadata.gz: e872009b6b4862fca29c9f3d432d1b60c6629b77d6f3a293983b56bb0afc249a
4
+ data.tar.gz: 3aa007580b800dde800072fb5b13ef21cc619800194f1a0c1d34f3d76db85ba8
5
5
  SHA512:
6
- metadata.gz: 7ab64d75d93daba3f7d514531a08b688f8442f444a2baa1f265a59d9968f90c4b0d2dc956f0b7a5adf3e1d4c25c0b3d8678664648617b0605164c22919d935db
7
- data.tar.gz: feb7f7b9df02aa100f0a63e3b302d045a38c62577b0a121ce28e0db4f0ea7e9e21a11ab89e5cb3a9e10d0f0ac6c05f3cbd8aba5192c9b3990aa621563c1e3ee5
6
+ metadata.gz: e229aad8ab09a80b14abf54abce5d4d205c71b3b2ad2105cd03a3a152ba4d6ee5eefc5d5538bbd6ebf4dda0892fb9073a92e9cd4334d76521533f08c2e47320f
7
+ data.tar.gz: ac047d82d5997039b26db6b1848cd151bb662c96837210c233614fdb4e170ee65daad81cf6048d5f3656468bafacad4327f2117c6556c3629973b0470c420087
data/README.md CHANGED
@@ -133,9 +133,53 @@ if token.expired?
133
133
  end
134
134
  ```
135
135
 
136
+ ### Resource-First Discovery (RFC 9728 + RFC 8414)
137
+
138
+ BC5's Authorization Server (AS) metadata lives only at the canonical issuer (the
139
+ web host), so discovery starts from the **resource** (the API host) rather than
140
+ probing the API host for AS metadata. Three composable operations are provided:
141
+
142
+ ```ruby
143
+ # RFC 8414 — AS metadata for a known issuer, bound to the requested issuer by
144
+ # code-point. token_endpoint is required; authorization_endpoint is OPTIONAL
145
+ # (device-only servers omit it) — authorization-code consumers must assert it.
146
+ config = Basecamp::Oauth.discover("https://launchpad.37signals.com")
147
+
148
+ # RFC 9728 — protected-resource metadata for a resource origin. resource is
149
+ # bound by code-point; authorization_servers preserves absent (nil) vs [].
150
+ resource = Basecamp::Oauth.discover_protected_resource("https://3.basecampapi.com")
151
+
152
+ # Orchestrator — resource-first selection + stage-sensitive fallback.
153
+ result = Basecamp::Oauth.discover_from_resource(
154
+ "https://3.basecampapi.com",
155
+ expected_issuer: nil # optional: authoritative issuer selection
156
+ )
157
+
158
+ if result.selected?
159
+ config = result.config # bound AS config for the selected issuer
160
+ else
161
+ # Only two SOFT reasons ever yield a fallback (→ Launchpad):
162
+ # "resource_discovery_failed" | "no_as_advertised"
163
+ result.reason
164
+ end
165
+ ```
166
+
167
+ `discover_from_resource` returns a `DiscoveryResult` that is either **selected**
168
+ or a **soft fallback**. Every *hard* failure — an ambiguous advertised set, an
169
+ unavailable `expected_issuer`, an invalid advertised issuer origin, an AS-metadata
170
+ fetch failure, or an issuer-binding mismatch after a BC5 issuer was selected —
171
+ raises `Basecamp::Oauth::DiscoverySelectionError` (carrying a `reason`). A hard
172
+ failure is **never** silently converted into a Launchpad request.
173
+
174
+ All discovery fetches are SSRF-hardened: origins are validated against the
175
+ origin-root profile (HTTPS-only, localhost exempt) with Ruby's `URI` parser before
176
+ any socket opens, redirects are not followed, timeouts are bounded, non-2xx maps
177
+ to `api_error`, and the response body is read under a genuine streaming cap that
178
+ aborts before an oversized body is buffered.
179
+
136
180
  ## Services
137
181
 
138
- The SDK provides 37 services covering the complete Basecamp API:
182
+ The SDK provides 45 account-scoped services. The table below covers the common ones; see `lib/basecamp/generated/services/` for the authoritative, complete set:
139
183
 
140
184
  | Service | Description |
141
185
  |---------|-------------|
@@ -166,6 +210,7 @@ The SDK provides 37 services covering the complete Basecamp API:
166
210
  | `card_tables` | Card tables (kanban) |
167
211
  | `card_columns` | Card table columns |
168
212
  | `card_steps` | Card workflow steps |
213
+ | `wormholes` | Card table wormholes (cross-project moves) |
169
214
  | `lineup` | Card lineup view |
170
215
  | `tools` | Project dock tools |
171
216
  | `search` | Full-text search |
@@ -194,6 +239,25 @@ first_10 = account.todos.list(todolist_id: 456).take(10)
194
239
  all_projects = account.projects.list.to_a
195
240
  ```
196
241
 
242
+ ## Downloading Files
243
+
244
+ Fetch an upload's file content in one call. The SDK fetches the upload
245
+ metadata, then follows the authenticated-hop + 302 flow against the
246
+ signed storage URL.
247
+
248
+ ```ruby
249
+ result = account.uploads.download(upload_id: 1069479400)
250
+ File.binwrite("uploaded.bin", result.body)
251
+ # result.content_type, result.content_length, result.filename are also available
252
+ ```
253
+
254
+ For any authenticated download URL (e.g. a `download_url` you already
255
+ have in hand), use `AccountClient#download_url`:
256
+
257
+ ```ruby
258
+ result = account.download_url(url)
259
+ ```
260
+
197
261
  ## Retry Behavior
198
262
 
199
263
  GET requests automatically retry on transient failures with exponential backoff:
data/basecamp-sdk.gemspec CHANGED
@@ -37,10 +37,9 @@ Gem::Specification.new do |spec|
37
37
  spec.add_development_dependency 'minitest', '~> 6.0'
38
38
  spec.add_development_dependency 'rake', '~> 13.0'
39
39
  spec.add_development_dependency 'rubocop-37signals'
40
- spec.add_development_dependency 'simplecov', '~> 0.22'
40
+ spec.add_development_dependency 'simplecov', '~> 1.0'
41
41
  spec.add_development_dependency 'webmock', '~> 3.24'
42
42
  spec.add_development_dependency 'irb', '~> 1.15'
43
- spec.add_development_dependency 'rdoc', '~> 7.1'
44
- spec.add_development_dependency 'webrick', '~> 1.9'
43
+ spec.add_development_dependency 'rdoc', '~> 8.0'
45
44
  spec.add_development_dependency 'yard', '~> 0.9'
46
45
  end
@@ -431,6 +431,11 @@ module Basecamp
431
431
  service(:card_steps) { Services::CardStepsService.new(self) }
432
432
  end
433
433
 
434
+ # @return [Services::WormholesService]
435
+ def wormholes
436
+ service(:wormholes) { Services::WormholesService.new(self) }
437
+ end
438
+
434
439
  # @return [Services::TemplatesService]
435
440
  def templates
436
441
  service(:templates) { Services::TemplatesService.new(self) }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://basecamp.com/schemas/sdk-metadata.json",
3
3
  "version": "1.0.0",
4
- "generated": "2026-03-26T04:17:28Z",
4
+ "generated": "2026-07-23T19:34:23Z",
5
5
  "operations": {
6
6
  "GetAccount": {
7
7
  "retry": {
@@ -92,7 +92,7 @@
92
92
  "natural": true
93
93
  }
94
94
  },
95
- "ListWebhooks": {
95
+ "SetCardColumnColor": {
96
96
  "retry": {
97
97
  "maxAttempts": 3,
98
98
  "baseDelayMs": 1000,
@@ -102,13 +102,11 @@
102
102
  503
103
103
  ]
104
104
  },
105
- "pagination": {
106
- "style": "link",
107
- "totalCountHeader": "X-Total-Count",
108
- "maxPageSize": 50
105
+ "idempotent": {
106
+ "natural": true
109
107
  }
110
108
  },
111
- "CreateWebhook": {
109
+ "EnableCardColumnOnHold": {
112
110
  "retry": {
113
111
  "maxAttempts": 2,
114
112
  "baseDelayMs": 1000,
@@ -119,7 +117,7 @@
119
117
  ]
120
118
  }
121
119
  },
122
- "GetCard": {
120
+ "DisableCardColumnOnHold": {
123
121
  "retry": {
124
122
  "maxAttempts": 3,
125
123
  "baseDelayMs": 1000,
@@ -128,9 +126,12 @@
128
126
  429,
129
127
  503
130
128
  ]
129
+ },
130
+ "idempotent": {
131
+ "natural": true
131
132
  }
132
133
  },
133
- "UpdateCard": {
134
+ "UpdateWormhole": {
134
135
  "retry": {
135
136
  "maxAttempts": 3,
136
137
  "baseDelayMs": 1000,
@@ -144,18 +145,21 @@
144
145
  "natural": true
145
146
  }
146
147
  },
147
- "MoveCard": {
148
+ "DeleteWormhole": {
148
149
  "retry": {
149
- "maxAttempts": 2,
150
+ "maxAttempts": 3,
150
151
  "baseDelayMs": 1000,
151
152
  "backoff": "exponential",
152
153
  "retryOn": [
153
154
  429,
154
155
  503
155
156
  ]
157
+ },
158
+ "idempotent": {
159
+ "natural": true
156
160
  }
157
161
  },
158
- "RepositionCardStep": {
162
+ "CreateWormhole": {
159
163
  "retry": {
160
164
  "maxAttempts": 2,
161
165
  "baseDelayMs": 1000,
@@ -166,7 +170,7 @@
166
170
  ]
167
171
  }
168
172
  },
169
- "CreateCardStep": {
173
+ "CreateTool": {
170
174
  "retry": {
171
175
  "maxAttempts": 2,
172
176
  "baseDelayMs": 1000,
@@ -177,7 +181,7 @@
177
181
  ]
178
182
  }
179
183
  },
180
- "GetCardColumn": {
184
+ "ListWebhooks": {
181
185
  "retry": {
182
186
  "maxAttempts": 3,
183
187
  "baseDelayMs": 1000,
@@ -186,9 +190,25 @@
186
190
  429,
187
191
  503
188
192
  ]
193
+ },
194
+ "pagination": {
195
+ "style": "link",
196
+ "totalCountHeader": "X-Total-Count",
197
+ "maxPageSize": 50
189
198
  }
190
199
  },
191
- "UpdateCardColumn": {
200
+ "CreateWebhook": {
201
+ "retry": {
202
+ "maxAttempts": 2,
203
+ "baseDelayMs": 1000,
204
+ "backoff": "exponential",
205
+ "retryOn": [
206
+ 429,
207
+ 503
208
+ ]
209
+ }
210
+ },
211
+ "GetCard": {
192
212
  "retry": {
193
213
  "maxAttempts": 3,
194
214
  "baseDelayMs": 1000,
@@ -197,12 +217,9 @@
197
217
  429,
198
218
  503
199
219
  ]
200
- },
201
- "idempotent": {
202
- "natural": true
203
220
  }
204
221
  },
205
- "SetCardColumnColor": {
222
+ "UpdateCard": {
206
223
  "retry": {
207
224
  "maxAttempts": 3,
208
225
  "baseDelayMs": 1000,
@@ -216,7 +233,7 @@
216
233
  "natural": true
217
234
  }
218
235
  },
219
- "EnableCardColumnOnHold": {
236
+ "MoveCard": {
220
237
  "retry": {
221
238
  "maxAttempts": 2,
222
239
  "baseDelayMs": 1000,
@@ -227,7 +244,40 @@
227
244
  ]
228
245
  }
229
246
  },
230
- "DisableCardColumnOnHold": {
247
+ "RepositionCardStep": {
248
+ "retry": {
249
+ "maxAttempts": 2,
250
+ "baseDelayMs": 1000,
251
+ "backoff": "exponential",
252
+ "retryOn": [
253
+ 429,
254
+ 503
255
+ ]
256
+ }
257
+ },
258
+ "CreateCardStep": {
259
+ "retry": {
260
+ "maxAttempts": 2,
261
+ "baseDelayMs": 1000,
262
+ "backoff": "exponential",
263
+ "retryOn": [
264
+ 429,
265
+ 503
266
+ ]
267
+ }
268
+ },
269
+ "GetCardColumn": {
270
+ "retry": {
271
+ "maxAttempts": 3,
272
+ "baseDelayMs": 1000,
273
+ "backoff": "exponential",
274
+ "retryOn": [
275
+ 429,
276
+ 503
277
+ ]
278
+ }
279
+ },
280
+ "UpdateCardColumn": {
231
281
  "retry": {
232
282
  "maxAttempts": 3,
233
283
  "baseDelayMs": 1000,
@@ -565,6 +615,20 @@
565
615
  ]
566
616
  }
567
617
  },
618
+ "UpdateCampfireLine": {
619
+ "retry": {
620
+ "maxAttempts": 3,
621
+ "baseDelayMs": 1000,
622
+ "backoff": "exponential",
623
+ "retryOn": [
624
+ 429,
625
+ 503
626
+ ]
627
+ },
628
+ "idempotent": {
629
+ "natural": true
630
+ }
631
+ },
568
632
  "DeleteCampfireLine": {
569
633
  "retry": {
570
634
  "maxAttempts": 3,
@@ -728,17 +792,6 @@
728
792
  "natural": true
729
793
  }
730
794
  },
731
- "CloneTool": {
732
- "retry": {
733
- "maxAttempts": 2,
734
- "baseDelayMs": 1000,
735
- "backoff": "exponential",
736
- "retryOn": [
737
- 429,
738
- 503
739
- ]
740
- }
741
- },
742
795
  "GetTool": {
743
796
  "retry": {
744
797
  "maxAttempts": 3,
@@ -2329,7 +2382,7 @@
2329
2382
  ]
2330
2383
  }
2331
2384
  },
2332
- "UpdateTodo": {
2385
+ "ReplaceTodo": {
2333
2386
  "retry": {
2334
2387
  "maxAttempts": 3,
2335
2388
  "baseDelayMs": 1000,
@@ -2399,6 +2452,20 @@
2399
2452
  "natural": true
2400
2453
  }
2401
2454
  },
2455
+ "RepositionTodolist": {
2456
+ "retry": {
2457
+ "maxAttempts": 3,
2458
+ "baseDelayMs": 1000,
2459
+ "backoff": "exponential",
2460
+ "retryOn": [
2461
+ 429,
2462
+ 503
2463
+ ]
2464
+ },
2465
+ "idempotent": {
2466
+ "natural": true
2467
+ }
2468
+ },
2402
2469
  "GetTodoset": {
2403
2470
  "retry": {
2404
2471
  "maxAttempts": 3,
@@ -141,6 +141,19 @@ module Basecamp
141
141
  kwargs.compact
142
142
  end
143
143
 
144
+ # Like compact_params, but also drops empty arrays. For query filters an
145
+ # empty array means "no filter" and must be omitted entirely: Faraday's
146
+ # nested encoder would otherwise emit a bare `bucket_ids[]`, which Rails
147
+ # normalizes into a bogus `[0]` filter instead of the unfiltered request
148
+ # the caller intended. Not used for request bodies, where an explicit
149
+ # empty array (e.g. clearing a list) can be meaningful.
150
+ #
151
+ # @param hash [Hash] the input hash
152
+ # @return [Hash] hash with nil values and empty arrays removed
153
+ def compact_query_params(**kwargs)
154
+ kwargs.reject { |_, value| value.nil? || value == [] }
155
+ end
156
+
144
157
  # Build a bucket (project) path.
145
158
  # @param project_id [Integer, String] the project/bucket ID
146
159
  # @param path [String] the path suffix
@@ -86,7 +86,7 @@ module Basecamp
86
86
  # @return [Enumerator<Hash>] paginated results
87
87
  def list_lines(campfire_id:, sort: nil, direction: nil)
88
88
  wrap_paginated(service: "campfires", operation: "list_lines", is_mutation: false, resource_id: campfire_id) do
89
- params = compact_params(sort: sort, direction: direction)
89
+ params = compact_query_params(sort: sort, direction: direction)
90
90
  paginate("/chats/#{campfire_id}/lines.json", params: params)
91
91
  end
92
92
  end
@@ -112,7 +112,19 @@ module Basecamp
112
112
  end
113
113
  end
114
114
 
115
- # Delete a campfire line
115
+ # Update an existing campfire line; the content is always treated as rich text (HTML).
116
+ # @param campfire_id [Integer] campfire id ID
117
+ # @param line_id [Integer] line id ID
118
+ # @param content [String] The new line content, interpreted as rich text (HTML)
119
+ # @return [void]
120
+ def update_line(campfire_id:, line_id:, content:)
121
+ with_operation(service: "campfires", operation: "update_line", is_mutation: true, resource_id: line_id) do
122
+ http_put("/chats/#{campfire_id}/lines/#{line_id}", body: compact_params(content: content))
123
+ nil
124
+ end
125
+ end
126
+
127
+ # Delete a campfire line; allowed for the line's creator or an admin.
116
128
  # @param campfire_id [Integer] campfire id ID
117
129
  # @param line_id [Integer] line id ID
118
130
  # @return [void]
@@ -130,7 +142,7 @@ module Basecamp
130
142
  # @return [Enumerator<Hash>] paginated results
131
143
  def list_uploads(campfire_id:, sort: nil, direction: nil)
132
144
  wrap_paginated(service: "campfires", operation: "list_uploads", is_mutation: false, resource_id: campfire_id) do
133
- params = compact_params(sort: sort, direction: direction)
145
+ params = compact_query_params(sort: sort, direction: direction)
134
146
  paginate("/chats/#{campfire_id}/uploads.json", params: params)
135
147
  end
136
148
  end
@@ -7,51 +7,54 @@ module Basecamp
7
7
  # @generated from OpenAPI spec
8
8
  class CardColumnsService < BaseService
9
9
 
10
- # Get a card column by ID
10
+ # Set the color of a column
11
+ # @param bucket_id [Integer] bucket id ID
11
12
  # @param column_id [Integer] column id ID
13
+ # @param color [String] Valid colors: white, red, orange, yellow, green, blue, aqua, purple, gray, pink, brown
12
14
  # @return [Hash] response data
13
- def get(column_id:)
14
- with_operation(service: "cardcolumns", operation: "get", is_mutation: false, resource_id: column_id) do
15
- http_get("/card_tables/columns/#{column_id}").json
15
+ def set_color(bucket_id:, column_id:, color:)
16
+ with_operation(service: "cardcolumns", operation: "set_color", is_mutation: true, project_id: bucket_id, resource_id: column_id) do
17
+ http_put("/buckets/#{bucket_id}/card_tables/columns/#{column_id}/color.json", body: compact_params(color: color)).json
16
18
  end
17
19
  end
18
20
 
19
- # Update an existing column
21
+ # Enable on-hold section in a column
22
+ # @param bucket_id [Integer] bucket id ID
20
23
  # @param column_id [Integer] column id ID
21
- # @param title [String, nil] title
22
- # @param description [String, nil] description
23
24
  # @return [Hash] response data
24
- def update(column_id:, title: nil, description: nil)
25
- with_operation(service: "cardcolumns", operation: "update", is_mutation: true, resource_id: column_id) do
26
- http_put("/card_tables/columns/#{column_id}", body: compact_params(title: title, description: description)).json
25
+ def enable_on_hold(bucket_id:, column_id:)
26
+ with_operation(service: "cardcolumns", operation: "enable_on_hold", is_mutation: true, project_id: bucket_id, resource_id: column_id) do
27
+ http_post("/buckets/#{bucket_id}/card_tables/columns/#{column_id}/on_hold.json").json
27
28
  end
28
29
  end
29
30
 
30
- # Set the color of a column
31
+ # Disable on-hold section in a column
32
+ # @param bucket_id [Integer] bucket id ID
31
33
  # @param column_id [Integer] column id ID
32
- # @param color [String] Valid colors: white, red, orange, yellow, green, blue, aqua, purple, gray, pink, brown
33
34
  # @return [Hash] response data
34
- def set_color(column_id:, color:)
35
- with_operation(service: "cardcolumns", operation: "set_color", is_mutation: true, resource_id: column_id) do
36
- http_put("/card_tables/columns/#{column_id}/color.json", body: compact_params(color: color)).json
35
+ def disable_on_hold(bucket_id:, column_id:)
36
+ with_operation(service: "cardcolumns", operation: "disable_on_hold", is_mutation: true, project_id: bucket_id, resource_id: column_id) do
37
+ http_delete("/buckets/#{bucket_id}/card_tables/columns/#{column_id}/on_hold.json").json
37
38
  end
38
39
  end
39
40
 
40
- # Enable on-hold section in a column
41
+ # Get a card column by ID
41
42
  # @param column_id [Integer] column id ID
42
43
  # @return [Hash] response data
43
- def enable_on_hold(column_id:)
44
- with_operation(service: "cardcolumns", operation: "enable_on_hold", is_mutation: true, resource_id: column_id) do
45
- http_post("/card_tables/columns/#{column_id}/on_hold.json").json
44
+ def get(column_id:)
45
+ with_operation(service: "cardcolumns", operation: "get", is_mutation: false, resource_id: column_id) do
46
+ http_get("/card_tables/columns/#{column_id}").json
46
47
  end
47
48
  end
48
49
 
49
- # Disable on-hold section in a column
50
+ # Update an existing column
50
51
  # @param column_id [Integer] column id ID
52
+ # @param title [String, nil] title
53
+ # @param description [String, nil] description
51
54
  # @return [Hash] response data
52
- def disable_on_hold(column_id:)
53
- with_operation(service: "cardcolumns", operation: "disable_on_hold", is_mutation: true, resource_id: column_id) do
54
- http_delete("/card_tables/columns/#{column_id}/on_hold.json").json
55
+ def update(column_id:, title: nil, description: nil)
56
+ with_operation(service: "cardcolumns", operation: "update", is_mutation: true, resource_id: column_id) do
57
+ http_put("/card_tables/columns/#{column_id}", body: compact_params(title: title, description: description)).json
55
58
  end
56
59
  end
57
60
 
@@ -57,11 +57,12 @@ module Basecamp
57
57
  # Create a new question in a questionnaire
58
58
  # @param questionnaire_id [Integer] questionnaire id ID
59
59
  # @param title [String] title
60
- # @param schedule [String] schedule
60
+ # @param schedule [Hash] schedule
61
+ # @param visible_to_clients [Boolean, nil] visible to clients
61
62
  # @return [Hash] response data
62
- def create_question(questionnaire_id:, title:, schedule:)
63
+ def create_question(questionnaire_id:, title:, schedule:, visible_to_clients: nil)
63
64
  with_operation(service: "checkins", operation: "create_question", is_mutation: true, resource_id: questionnaire_id) do
64
- http_post("/questionnaires/#{questionnaire_id}/questions.json", body: compact_params(title: title, schedule: schedule)).json
65
+ http_post("/questionnaires/#{questionnaire_id}/questions.json", body: compact_params(title: title, schedule: schedule, visible_to_clients: visible_to_clients)).json
65
66
  end
66
67
  end
67
68
 
@@ -77,7 +78,7 @@ module Basecamp
77
78
  # Update an existing question
78
79
  # @param question_id [Integer] question id ID
79
80
  # @param title [String, nil] title
80
- # @param schedule [String, nil] schedule
81
+ # @param schedule [Hash, nil] schedule
81
82
  # @param paused [Boolean, nil] paused
82
83
  # @return [Hash] response data
83
84
  def update_question(question_id:, title: nil, schedule: nil, paused: nil)
@@ -13,7 +13,7 @@ module Basecamp
13
13
  # @return [Enumerator<Hash>] paginated results
14
14
  def list(sort: nil, direction: nil)
15
15
  wrap_paginated(service: "clientapprovals", operation: "list", is_mutation: false) do
16
- params = compact_params(sort: sort, direction: direction)
16
+ params = compact_query_params(sort: sort, direction: direction)
17
17
  paginate("/client/approvals.json", params: params)
18
18
  end
19
19
  end
@@ -13,7 +13,7 @@ module Basecamp
13
13
  # @return [Enumerator<Hash>] paginated results
14
14
  def list(sort: nil, direction: nil)
15
15
  wrap_paginated(service: "clientcorrespondences", operation: "list", is_mutation: false) do
16
- params = compact_params(sort: sort, direction: direction)
16
+ params = compact_query_params(sort: sort, direction: direction)
17
17
  paginate("/client/correspondences.json", params: params)
18
18
  end
19
19
  end
@@ -42,10 +42,11 @@ module Basecamp
42
42
  # @param content [String, nil] content
43
43
  # @param status [String, nil] active|drafted
44
44
  # @param subscriptions [Array, nil] subscriptions
45
+ # @param visible_to_clients [Boolean, nil] visible to clients
45
46
  # @return [Hash] response data
46
- def create(vault_id:, title:, content: nil, status: nil, subscriptions: nil)
47
+ def create(vault_id:, title:, content: nil, status: nil, subscriptions: nil, visible_to_clients: nil)
47
48
  with_operation(service: "documents", operation: "create", is_mutation: true, resource_id: vault_id) do
48
- http_post("/vaults/#{vault_id}/documents.json", body: compact_params(title: title, content: content, status: status, subscriptions: subscriptions)).json
49
+ http_post("/vaults/#{vault_id}/documents.json", body: compact_params(title: title, content: content, status: status, subscriptions: subscriptions, visible_to_clients: visible_to_clients)).json
49
50
  end
50
51
  end
51
52
  end
@@ -61,7 +61,7 @@ module Basecamp
61
61
  # @return [Enumerator<Hash>] paginated results
62
62
  def list(inbox_id:, sort: nil, direction: nil)
63
63
  wrap_paginated(service: "forwards", operation: "list", is_mutation: false, resource_id: inbox_id) do
64
- params = compact_params(sort: sort, direction: direction)
64
+ params = compact_query_params(sort: sort, direction: direction)
65
65
  paginate("/inboxes/#{inbox_id}/forwards.json", params: params)
66
66
  end
67
67
  end
@@ -18,7 +18,7 @@ module Basecamp
18
18
 
19
19
  # Update a gauge needle's description. Position and color are immutable.
20
20
  # @param needle_id [Integer] needle id ID
21
- # @param gauge_needle [String, nil] gauge needle
21
+ # @param gauge_needle [Hash, nil] gauge needle
22
22
  # @return [Hash] response data
23
23
  def update_gauge_needle(needle_id:, gauge_needle: nil)
24
24
  with_operation(service: "gauges", operation: "update_gauge_needle", is_mutation: true, resource_id: needle_id) do
@@ -38,7 +38,7 @@ module Basecamp
38
38
 
39
39
  # Enable or disable the gauge for a project. Only project admins can toggle gauges.
40
40
  # @param project_id [Integer] project id ID
41
- # @param gauge [String] gauge
41
+ # @param gauge [Hash] gauge
42
42
  # @return [void]
43
43
  def toggle_gauge(project_id:, gauge:)
44
44
  with_operation(service: "gauges", operation: "toggle_gauge", is_mutation: true, project_id: project_id) do
@@ -58,7 +58,7 @@ module Basecamp
58
58
 
59
59
  # Create a gauge needle (progress update) for a project
60
60
  # @param project_id [Integer] project id ID
61
- # @param gauge_needle [String] gauge needle
61
+ # @param gauge_needle [Hash] gauge needle
62
62
  # @param notify [String, nil] Who to notify: "everyone", "working_on", "custom", or omit for nobody
63
63
  # @param subscriptions [Array, nil] Array of people IDs to notify (only used when notify is "custom")
64
64
  # @return [Hash] response data
@@ -74,7 +74,7 @@ module Basecamp
74
74
  # @return [Enumerator<Hash>] paginated results
75
75
  def list_gauges(bucket_ids: nil)
76
76
  wrap_paginated(service: "gauges", operation: "list_gauges", is_mutation: false) do
77
- params = compact_params(bucket_ids: bucket_ids)
77
+ params = compact_query_params(bucket_ids: bucket_ids)
78
78
  paginate("/reports/gauges.json", params: params)
79
79
  end
80
80
  end
@@ -14,7 +14,7 @@ module Basecamp
14
14
  # @return [Enumerator<Hash>] paginated results
15
15
  def list(board_id:, sort: nil, direction: nil)
16
16
  wrap_paginated(service: "messages", operation: "list", is_mutation: false, resource_id: board_id) do
17
- params = compact_params(sort: sort, direction: direction)
17
+ params = compact_query_params(sort: sort, direction: direction)
18
18
  paginate("/message_boards/#{board_id}/messages.json", params: params)
19
19
  end
20
20
  end
@@ -26,10 +26,11 @@ module Basecamp
26
26
  # @param status [String, nil] active|drafted
27
27
  # @param category_id [Integer, nil] category id
28
28
  # @param subscriptions [Array, nil] subscriptions
29
+ # @param visible_to_clients [Boolean, nil] visible to clients
29
30
  # @return [Hash] response data
30
- def create(board_id:, subject:, content: nil, status: nil, category_id: nil, subscriptions: nil)
31
+ def create(board_id:, subject:, content: nil, status: nil, category_id: nil, subscriptions: nil, visible_to_clients: nil)
31
32
  with_operation(service: "messages", operation: "create", is_mutation: true, resource_id: board_id) do
32
- http_post("/message_boards/#{board_id}/messages.json", body: compact_params(subject: subject, content: content, status: status, category_id: category_id, subscriptions: subscriptions)).json
33
+ http_post("/message_boards/#{board_id}/messages.json", body: compact_params(subject: subject, content: content, status: status, category_id: category_id, subscriptions: subscriptions, visible_to_clients: visible_to_clients)).json
33
34
  end
34
35
  end
35
36
 
@@ -29,7 +29,7 @@ module Basecamp
29
29
  # @return [Hash] response data
30
30
  def get_my_due_assignments(scope: nil)
31
31
  with_operation(service: "myassignments", operation: "get_my_due_assignments", is_mutation: false) do
32
- http_get("/my/assignments/due.json", params: compact_params(scope: scope)).json
32
+ http_get("/my/assignments/due.json", params: compact_query_params(scope: scope)).json
33
33
  end
34
34
  end
35
35
  end