basecamp-sdk 0.7.3 → 0.8.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 +63 -0
- data/basecamp-sdk.gemspec +2 -3
- data/lib/basecamp/generated/metadata.json +57 -43
- data/lib/basecamp/generated/services/campfires_service.rb +13 -1
- data/lib/basecamp/generated/services/card_columns_service.rb +26 -23
- data/lib/basecamp/generated/services/reports_service.rb +1 -1
- data/lib/basecamp/generated/services/todos_service.rb +4 -4
- data/lib/basecamp/generated/services/tools_service.rb +7 -6
- data/lib/basecamp/generated/services/uploads_service.rb +17 -0
- data/lib/basecamp/generated/types.rb +32 -8
- data/lib/basecamp/http.rb +86 -15
- data/lib/basecamp/oauth/config.rb +20 -5
- data/lib/basecamp/oauth/discovery.rb +134 -64
- data/lib/basecamp/oauth/discovery_result.rb +33 -0
- data/lib/basecamp/oauth/discovery_selection_error.rb +30 -0
- data/lib/basecamp/oauth/fetcher.rb +204 -0
- data/lib/basecamp/oauth/protected_resource_metadata.rb +22 -0
- data/lib/basecamp/oauth/resource.rb +95 -0
- data/lib/basecamp/oauth.rb +173 -0
- data/lib/basecamp/security.rb +83 -0
- data/lib/basecamp/services/authorization_service.rb +45 -0
- data/lib/basecamp/services/todos_extensions.rb +121 -0
- data/lib/basecamp/version.rb +2 -2
- data/lib/basecamp.rb +6 -0
- data/scripts/generate-services.rb +37 -3
- metadata +13 -21
- 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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 50a6f193dfed4631d675333812b23a9d586b5f78d2a060bbd15222855c93f62c
|
|
4
|
+
data.tar.gz: 2a9ce91aff3ae13f3262701feaa2d7706f100c1d217714896bfad94006563daf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a594aada84cb34e591e3ec6747095d799626fb37763531c14c3d7a0fdc05360e61572e6e3e9748c405c73e4c603ca30c503b22b73c24273cfc13de84891a8f9b
|
|
7
|
+
data.tar.gz: 60a1d05afbe172cd92b4ee5c75871e57df33cb52f3f664e4c0dfa2823259f7ef5593cb32e022e41cc73ee30b255b214c16c4e2d1901ceef3f48eed6e11075f3b
|
data/README.md
CHANGED
|
@@ -133,6 +133,50 @@ 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
182
|
The SDK provides 37 services covering the complete Basecamp API:
|
|
@@ -194,6 +238,25 @@ first_10 = account.todos.list(todolist_id: 456).take(10)
|
|
|
194
238
|
all_projects = account.projects.list.to_a
|
|
195
239
|
```
|
|
196
240
|
|
|
241
|
+
## Downloading Files
|
|
242
|
+
|
|
243
|
+
Fetch an upload's file content in one call. The SDK fetches the upload
|
|
244
|
+
metadata, then follows the authenticated-hop + 302 flow against the
|
|
245
|
+
signed storage URL.
|
|
246
|
+
|
|
247
|
+
```ruby
|
|
248
|
+
result = account.uploads.download(upload_id: 1069479400)
|
|
249
|
+
File.binwrite("uploaded.bin", result.body)
|
|
250
|
+
# result.content_type, result.content_length, result.filename are also available
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
For any authenticated download URL (e.g. a `download_url` you already
|
|
254
|
+
have in hand), use `AccountClient#download_url`:
|
|
255
|
+
|
|
256
|
+
```ruby
|
|
257
|
+
result = account.download_url(url)
|
|
258
|
+
```
|
|
259
|
+
|
|
197
260
|
## Retry Behavior
|
|
198
261
|
|
|
199
262
|
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
|
|
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', '~>
|
|
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
|
|
@@ -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-
|
|
4
|
+
"generated": "2026-07-22T08:33:01Z",
|
|
5
5
|
"operations": {
|
|
6
6
|
"GetAccount": {
|
|
7
7
|
"retry": {
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
"natural": true
|
|
93
93
|
}
|
|
94
94
|
},
|
|
95
|
-
"
|
|
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
|
-
"
|
|
106
|
-
"
|
|
107
|
-
"totalCountHeader": "X-Total-Count",
|
|
108
|
-
"maxPageSize": 50
|
|
105
|
+
"idempotent": {
|
|
106
|
+
"natural": true
|
|
109
107
|
}
|
|
110
108
|
},
|
|
111
|
-
"
|
|
109
|
+
"EnableCardColumnOnHold": {
|
|
112
110
|
"retry": {
|
|
113
111
|
"maxAttempts": 2,
|
|
114
112
|
"baseDelayMs": 1000,
|
|
@@ -119,7 +117,7 @@
|
|
|
119
117
|
]
|
|
120
118
|
}
|
|
121
119
|
},
|
|
122
|
-
"
|
|
120
|
+
"DisableCardColumnOnHold": {
|
|
123
121
|
"retry": {
|
|
124
122
|
"maxAttempts": 3,
|
|
125
123
|
"baseDelayMs": 1000,
|
|
@@ -128,34 +126,39 @@
|
|
|
128
126
|
429,
|
|
129
127
|
503
|
|
130
128
|
]
|
|
129
|
+
},
|
|
130
|
+
"idempotent": {
|
|
131
|
+
"natural": true
|
|
131
132
|
}
|
|
132
133
|
},
|
|
133
|
-
"
|
|
134
|
+
"CreateTool": {
|
|
134
135
|
"retry": {
|
|
135
|
-
"maxAttempts":
|
|
136
|
+
"maxAttempts": 2,
|
|
136
137
|
"baseDelayMs": 1000,
|
|
137
138
|
"backoff": "exponential",
|
|
138
139
|
"retryOn": [
|
|
139
140
|
429,
|
|
140
141
|
503
|
|
141
142
|
]
|
|
142
|
-
},
|
|
143
|
-
"idempotent": {
|
|
144
|
-
"natural": true
|
|
145
143
|
}
|
|
146
144
|
},
|
|
147
|
-
"
|
|
145
|
+
"ListWebhooks": {
|
|
148
146
|
"retry": {
|
|
149
|
-
"maxAttempts":
|
|
147
|
+
"maxAttempts": 3,
|
|
150
148
|
"baseDelayMs": 1000,
|
|
151
149
|
"backoff": "exponential",
|
|
152
150
|
"retryOn": [
|
|
153
151
|
429,
|
|
154
152
|
503
|
|
155
153
|
]
|
|
154
|
+
},
|
|
155
|
+
"pagination": {
|
|
156
|
+
"style": "link",
|
|
157
|
+
"totalCountHeader": "X-Total-Count",
|
|
158
|
+
"maxPageSize": 50
|
|
156
159
|
}
|
|
157
160
|
},
|
|
158
|
-
"
|
|
161
|
+
"CreateWebhook": {
|
|
159
162
|
"retry": {
|
|
160
163
|
"maxAttempts": 2,
|
|
161
164
|
"baseDelayMs": 1000,
|
|
@@ -166,9 +169,9 @@
|
|
|
166
169
|
]
|
|
167
170
|
}
|
|
168
171
|
},
|
|
169
|
-
"
|
|
172
|
+
"GetCard": {
|
|
170
173
|
"retry": {
|
|
171
|
-
"maxAttempts":
|
|
174
|
+
"maxAttempts": 3,
|
|
172
175
|
"baseDelayMs": 1000,
|
|
173
176
|
"backoff": "exponential",
|
|
174
177
|
"retryOn": [
|
|
@@ -177,7 +180,7 @@
|
|
|
177
180
|
]
|
|
178
181
|
}
|
|
179
182
|
},
|
|
180
|
-
"
|
|
183
|
+
"UpdateCard": {
|
|
181
184
|
"retry": {
|
|
182
185
|
"maxAttempts": 3,
|
|
183
186
|
"baseDelayMs": 1000,
|
|
@@ -186,37 +189,34 @@
|
|
|
186
189
|
429,
|
|
187
190
|
503
|
|
188
191
|
]
|
|
192
|
+
},
|
|
193
|
+
"idempotent": {
|
|
194
|
+
"natural": true
|
|
189
195
|
}
|
|
190
196
|
},
|
|
191
|
-
"
|
|
197
|
+
"MoveCard": {
|
|
192
198
|
"retry": {
|
|
193
|
-
"maxAttempts":
|
|
199
|
+
"maxAttempts": 2,
|
|
194
200
|
"baseDelayMs": 1000,
|
|
195
201
|
"backoff": "exponential",
|
|
196
202
|
"retryOn": [
|
|
197
203
|
429,
|
|
198
204
|
503
|
|
199
205
|
]
|
|
200
|
-
},
|
|
201
|
-
"idempotent": {
|
|
202
|
-
"natural": true
|
|
203
206
|
}
|
|
204
207
|
},
|
|
205
|
-
"
|
|
208
|
+
"RepositionCardStep": {
|
|
206
209
|
"retry": {
|
|
207
|
-
"maxAttempts":
|
|
210
|
+
"maxAttempts": 2,
|
|
208
211
|
"baseDelayMs": 1000,
|
|
209
212
|
"backoff": "exponential",
|
|
210
213
|
"retryOn": [
|
|
211
214
|
429,
|
|
212
215
|
503
|
|
213
216
|
]
|
|
214
|
-
},
|
|
215
|
-
"idempotent": {
|
|
216
|
-
"natural": true
|
|
217
217
|
}
|
|
218
218
|
},
|
|
219
|
-
"
|
|
219
|
+
"CreateCardStep": {
|
|
220
220
|
"retry": {
|
|
221
221
|
"maxAttempts": 2,
|
|
222
222
|
"baseDelayMs": 1000,
|
|
@@ -227,7 +227,18 @@
|
|
|
227
227
|
]
|
|
228
228
|
}
|
|
229
229
|
},
|
|
230
|
-
"
|
|
230
|
+
"GetCardColumn": {
|
|
231
|
+
"retry": {
|
|
232
|
+
"maxAttempts": 3,
|
|
233
|
+
"baseDelayMs": 1000,
|
|
234
|
+
"backoff": "exponential",
|
|
235
|
+
"retryOn": [
|
|
236
|
+
429,
|
|
237
|
+
503
|
|
238
|
+
]
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
"UpdateCardColumn": {
|
|
231
242
|
"retry": {
|
|
232
243
|
"maxAttempts": 3,
|
|
233
244
|
"baseDelayMs": 1000,
|
|
@@ -565,6 +576,20 @@
|
|
|
565
576
|
]
|
|
566
577
|
}
|
|
567
578
|
},
|
|
579
|
+
"UpdateCampfireLine": {
|
|
580
|
+
"retry": {
|
|
581
|
+
"maxAttempts": 3,
|
|
582
|
+
"baseDelayMs": 1000,
|
|
583
|
+
"backoff": "exponential",
|
|
584
|
+
"retryOn": [
|
|
585
|
+
429,
|
|
586
|
+
503
|
|
587
|
+
]
|
|
588
|
+
},
|
|
589
|
+
"idempotent": {
|
|
590
|
+
"natural": true
|
|
591
|
+
}
|
|
592
|
+
},
|
|
568
593
|
"DeleteCampfireLine": {
|
|
569
594
|
"retry": {
|
|
570
595
|
"maxAttempts": 3,
|
|
@@ -728,17 +753,6 @@
|
|
|
728
753
|
"natural": true
|
|
729
754
|
}
|
|
730
755
|
},
|
|
731
|
-
"CloneTool": {
|
|
732
|
-
"retry": {
|
|
733
|
-
"maxAttempts": 2,
|
|
734
|
-
"baseDelayMs": 1000,
|
|
735
|
-
"backoff": "exponential",
|
|
736
|
-
"retryOn": [
|
|
737
|
-
429,
|
|
738
|
-
503
|
|
739
|
-
]
|
|
740
|
-
}
|
|
741
|
-
},
|
|
742
756
|
"GetTool": {
|
|
743
757
|
"retry": {
|
|
744
758
|
"maxAttempts": 3,
|
|
@@ -2329,7 +2343,7 @@
|
|
|
2329
2343
|
]
|
|
2330
2344
|
}
|
|
2331
2345
|
},
|
|
2332
|
-
"
|
|
2346
|
+
"ReplaceTodo": {
|
|
2333
2347
|
"retry": {
|
|
2334
2348
|
"maxAttempts": 3,
|
|
2335
2349
|
"baseDelayMs": 1000,
|
|
@@ -112,7 +112,19 @@ module Basecamp
|
|
|
112
112
|
end
|
|
113
113
|
end
|
|
114
114
|
|
|
115
|
-
#
|
|
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]
|
|
@@ -7,51 +7,54 @@ module Basecamp
|
|
|
7
7
|
# @generated from OpenAPI spec
|
|
8
8
|
class CardColumnsService < BaseService
|
|
9
9
|
|
|
10
|
-
#
|
|
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
|
|
14
|
-
with_operation(service: "cardcolumns", operation: "
|
|
15
|
-
|
|
15
|
+
def set_color(bucket_id:, column_id:, color:)
|
|
16
|
+
with_operation(service: "cardcolumns", operation: "set_color", is_mutation: true, 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
|
-
#
|
|
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
|
|
25
|
-
with_operation(service: "cardcolumns", operation: "
|
|
26
|
-
|
|
25
|
+
def enable_on_hold(bucket_id:, column_id:)
|
|
26
|
+
with_operation(service: "cardcolumns", operation: "enable_on_hold", is_mutation: true, 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
|
-
#
|
|
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
|
|
35
|
-
with_operation(service: "cardcolumns", operation: "
|
|
36
|
-
|
|
35
|
+
def disable_on_hold(bucket_id:, column_id:)
|
|
36
|
+
with_operation(service: "cardcolumns", operation: "disable_on_hold", is_mutation: true, 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
|
-
#
|
|
41
|
+
# Get a card column by ID
|
|
41
42
|
# @param column_id [Integer] column id ID
|
|
42
43
|
# @return [Hash] response data
|
|
43
|
-
def
|
|
44
|
-
with_operation(service: "cardcolumns", operation: "
|
|
45
|
-
|
|
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
|
-
#
|
|
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
|
|
53
|
-
with_operation(service: "cardcolumns", operation: "
|
|
54
|
-
|
|
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
|
|
|
@@ -15,7 +15,7 @@ module Basecamp
|
|
|
15
15
|
end
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
-
# Get upcoming schedule entries within a date window
|
|
18
|
+
# Get upcoming schedule entries and assignable items within a date window.
|
|
19
19
|
# @param window_starts_on [String, nil] window starts on
|
|
20
20
|
# @param window_ends_on [String, nil] window ends on
|
|
21
21
|
# @return [Hash] response data
|
|
@@ -44,9 +44,9 @@ module Basecamp
|
|
|
44
44
|
end
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
-
#
|
|
47
|
+
# Replace a todo with a new complete representation.
|
|
48
48
|
# @param todo_id [Integer] todo id ID
|
|
49
|
-
# @param content [String
|
|
49
|
+
# @param content [String] content
|
|
50
50
|
# @param description [String, nil] description
|
|
51
51
|
# @param assignee_ids [Array, nil] assignee ids
|
|
52
52
|
# @param completion_subscriber_ids [Array, nil] completion subscriber ids
|
|
@@ -54,8 +54,8 @@ module Basecamp
|
|
|
54
54
|
# @param due_on [String, nil] due on (YYYY-MM-DD)
|
|
55
55
|
# @param starts_on [String, nil] starts on (YYYY-MM-DD)
|
|
56
56
|
# @return [Hash] response data
|
|
57
|
-
def
|
|
58
|
-
with_operation(service: "todos", operation: "
|
|
57
|
+
def replace(todo_id:, content:, description: nil, assignee_ids: nil, completion_subscriber_ids: nil, notify: nil, due_on: nil, starts_on: nil)
|
|
58
|
+
with_operation(service: "todos", operation: "replace", is_mutation: true, resource_id: todo_id) do
|
|
59
59
|
http_put("/todos/#{todo_id}", body: compact_params(content: content, description: description, assignee_ids: assignee_ids, completion_subscriber_ids: completion_subscriber_ids, notify: notify, due_on: due_on, starts_on: starts_on)).json
|
|
60
60
|
end
|
|
61
61
|
end
|
|
@@ -7,13 +7,14 @@ module Basecamp
|
|
|
7
7
|
# @generated from OpenAPI spec
|
|
8
8
|
class ToolsService < BaseService
|
|
9
9
|
|
|
10
|
-
#
|
|
11
|
-
# @param
|
|
12
|
-
# @param
|
|
10
|
+
# Create a tool in a project dock
|
|
11
|
+
# @param bucket_id [Integer] bucket id ID
|
|
12
|
+
# @param tool_type [String] Tool type to add to the project dock. Values: Chat::Transcript|Inbox|Kanban::Board|Message::Board|Questionnaire|Schedule|Todoset|Vault.
|
|
13
|
+
# @param title [String, nil] Title for the new tool. When omitted, Basecamp assigns the next available default title for the tool type.
|
|
13
14
|
# @return [Hash] response data
|
|
14
|
-
def
|
|
15
|
-
with_operation(service: "tools", operation: "
|
|
16
|
-
http_post("/dock/tools.json", body: compact_params(
|
|
15
|
+
def create(bucket_id:, tool_type:, title: nil)
|
|
16
|
+
with_operation(service: "tools", operation: "create", is_mutation: true, resource_id: bucket_id) do
|
|
17
|
+
http_post("/buckets/#{bucket_id}/dock/tools.json", body: compact_params(tool_type: tool_type, title: title)).json
|
|
17
18
|
end
|
|
18
19
|
end
|
|
19
20
|
|
|
@@ -57,6 +57,23 @@ module Basecamp
|
|
|
57
57
|
http_post("/vaults/#{vault_id}/uploads.json", body: compact_params(attachable_sgid: attachable_sgid, description: description, base_name: base_name, subscriptions: subscriptions)).json
|
|
58
58
|
end
|
|
59
59
|
end
|
|
60
|
+
|
|
61
|
+
# Download an upload's file content in one call.
|
|
62
|
+
# Fetches upload metadata, then delegates to the AccountClient download
|
|
63
|
+
# primitive so the auth'd-hop + 302-follow flow lives in one place.
|
|
64
|
+
# @param upload_id [Integer] upload id ID
|
|
65
|
+
# @return [Basecamp::DownloadResult]
|
|
66
|
+
def download(upload_id:)
|
|
67
|
+
with_operation(service: "uploads", operation: "download", is_mutation: false, resource_id: upload_id) do
|
|
68
|
+
upload = get(upload_id: upload_id)
|
|
69
|
+
url = upload["download_url"]
|
|
70
|
+
raise UsageError.new("upload #{upload_id} has no download_url") if url.nil? || url.empty?
|
|
71
|
+
|
|
72
|
+
result = @client.download_url(url)
|
|
73
|
+
filename = upload["filename"]
|
|
74
|
+
filename.to_s.empty? ? result : result.with(filename: filename)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
60
77
|
end
|
|
61
78
|
end
|
|
62
79
|
end
|