basecamp-sdk 0.14.0 → 0.16.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 +1 -1
- data/lib/basecamp/api_error.rb +3 -2
- data/lib/basecamp/client.rb +54 -4
- data/lib/basecamp/config.rb +25 -3
- data/lib/basecamp/generated/metadata.json +115 -1
- data/lib/basecamp/generated/services/bubble_ups_service.rb +36 -0
- data/lib/basecamp/generated/services/projects_service.rb +18 -0
- data/lib/basecamp/generated/services/recordings_service.rb +19 -0
- data/lib/basecamp/generated/services/templates_service.rb +28 -0
- data/lib/basecamp/generated/types.rb +240 -28
- data/lib/basecamp/http.rb +94 -25
- data/lib/basecamp/oauth/exchange.rb +152 -25
- data/lib/basecamp/oauth/fetcher.rb +1 -1
- data/lib/basecamp/oauth_token_provider.rb +50 -13
- data/lib/basecamp/people_confirmation_required_error.rb +14 -0
- data/lib/basecamp/rate_limit_error.rb +4 -2
- data/lib/basecamp/security.rb +28 -2
- data/lib/basecamp/services/authorization_service.rb +5 -3
- data/lib/basecamp/version.rb +2 -2
- data/lib/basecamp.rb +63 -11
- data/scripts/generate-services.rb +7 -1
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 67cf91504a5aadff973463d83b4a4de98aa4ad35e2dd7d0d4e5f4f26021e8423
|
|
4
|
+
data.tar.gz: 88eacbb6628831a01e6a9cb56503618377ffec4a85ad85d5a514b025ff53dd2a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8cede9f49e112c50b65d7c7c4901f442cba400211bfe4676a9af95a95ac4ca5da1a1477c529f72456745a0f8423b766162c151135b89a2c52ce1abbacb9eb17f
|
|
7
|
+
data.tar.gz: 695198c7697799719f0d66657463ed8ad76739bcd3bbe11877ad0a16d1fdd6b20602e2d6efa623c9d6e05a2b78ef5528e346ebec5975dd1697e5379e96b1e640
|
data/README.md
CHANGED
|
@@ -316,7 +316,7 @@ aborts before an oversized body is buffered.
|
|
|
316
316
|
|
|
317
317
|
## Services
|
|
318
318
|
|
|
319
|
-
The SDK provides
|
|
319
|
+
The SDK provides the full account-scoped service set documented in SPEC §5. The table below covers the common ones; see `lib/basecamp/generated/services/` for the authoritative, complete set:
|
|
320
320
|
|
|
321
321
|
| Service | Description |
|
|
322
322
|
|---------|-------------|
|
data/lib/basecamp/api_error.rb
CHANGED
|
@@ -17,11 +17,12 @@ module Basecamp
|
|
|
17
17
|
# Creates an ApiError from an HTTP status code.
|
|
18
18
|
# @param status [Integer] HTTP status code
|
|
19
19
|
# @param message [String, nil] optional error message
|
|
20
|
+
# @param hint [String, nil] optional hint (SPEC section 6 step 3)
|
|
20
21
|
# @return [ApiError]
|
|
21
|
-
def self.from_status(status, message = nil)
|
|
22
|
+
def self.from_status(status, message = nil, hint: nil)
|
|
22
23
|
message ||= "Request failed (HTTP #{status})"
|
|
23
24
|
retryable = status >= 500 && status < 600
|
|
24
|
-
new(message, http_status: status, retryable: retryable)
|
|
25
|
+
new(message, http_status: status, hint: hint, retryable: retryable)
|
|
25
26
|
end
|
|
26
27
|
end
|
|
27
28
|
end
|
data/lib/basecamp/client.rb
CHANGED
|
@@ -240,7 +240,9 @@ module Basecamp
|
|
|
240
240
|
#
|
|
241
241
|
# Handles the full download flow: URL rewriting to the configured API host,
|
|
242
242
|
# authenticated first hop (which typically 302s to a signed download URL),
|
|
243
|
-
# and unauthenticated second hop to fetch the actual file content.
|
|
243
|
+
# and unauthenticated second hop to fetch the actual file content. Neither
|
|
244
|
+
# hop follows a redirect on its own: hop 1's is the dispatch to hop 2, and
|
|
245
|
+
# a redirect on hop 2 is an error (SPEC §14 "Hop-2 Redirect Policy").
|
|
244
246
|
#
|
|
245
247
|
# @param raw_url [String] absolute download URL (e.g., from bc-attachment elements)
|
|
246
248
|
# @return [DownloadResult] the download result with body, content_type, content_length, filename
|
|
@@ -505,6 +507,11 @@ module Basecamp
|
|
|
505
507
|
service(:bookmarks) { Services::BookmarksService.new(self) }
|
|
506
508
|
end
|
|
507
509
|
|
|
510
|
+
# @return [Services::BubbleUpsService]
|
|
511
|
+
def bubble_ups
|
|
512
|
+
service(:bubble_ups) { Services::BubbleUpsService.new(self) }
|
|
513
|
+
end
|
|
514
|
+
|
|
508
515
|
# @return [Services::FoldersService]
|
|
509
516
|
def folders
|
|
510
517
|
service(:folders) { Services::FoldersService.new(self) }
|
|
@@ -616,7 +623,35 @@ module Basecamp
|
|
|
616
623
|
end
|
|
617
624
|
|
|
618
625
|
def fetch_signed_download(url)
|
|
619
|
-
|
|
626
|
+
# The Location target is server-supplied, and this unauthenticated hop
|
|
627
|
+
# has no same_origin? gate to refuse it (a signed URL is legitimately
|
|
628
|
+
# cross-origin), so illegible targets must be refused here — as
|
|
629
|
+
# ApiError, matching the legible refusals Go and TypeScript surface
|
|
630
|
+
# when their dial rejects the scheme. One predicate states the whole
|
|
631
|
+
# invariant — a dialable absolute HTTP(S) URL — because piecemeal
|
|
632
|
+
# guards kept leaking: "it parses" admitted "ftp://", and "scheme is
|
|
633
|
+
# http(s)" admitted hostless "http:foo", which Net::HTTP::Get rejects
|
|
634
|
+
# with a raw ArgumentError before the dial (nothing is ever sent).
|
|
635
|
+
# URI.parse types the scheme (URI::HTTPS < URI::HTTP), and a nil or
|
|
636
|
+
# empty host ("http:foo", "http:///x") is undialable.
|
|
637
|
+
uri = begin
|
|
638
|
+
URI.parse(url)
|
|
639
|
+
rescue URI::Error
|
|
640
|
+
nil
|
|
641
|
+
end
|
|
642
|
+
unless uri.is_a?(URI::HTTP) && uri.host && !uri.host.empty?
|
|
643
|
+
# SPEC §9: the signed URL is a credential — render its origin alone,
|
|
644
|
+
# projected from the parse, or the fixed token when no complete origin
|
|
645
|
+
# exists. Truncating the whole URL kept a short query intact.
|
|
646
|
+
origin = if uri&.scheme && uri&.host && !uri.host.empty?
|
|
647
|
+
port = uri.port && uri.port != uri.default_port ? ":#{uri.port}" : ""
|
|
648
|
+
"#{uri.scheme}://#{uri.host}#{port}"
|
|
649
|
+
else
|
|
650
|
+
"unparsable"
|
|
651
|
+
end
|
|
652
|
+
raise ApiError.new("redirect to undialable download URL: #{origin}")
|
|
653
|
+
end
|
|
654
|
+
|
|
620
655
|
http_client = Net::HTTP.new(uri.host, uri.port)
|
|
621
656
|
http_client.use_ssl = (uri.scheme == "https")
|
|
622
657
|
http_client.open_timeout = config.timeout
|
|
@@ -625,9 +660,24 @@ module Basecamp
|
|
|
625
660
|
request = Net::HTTP::Get.new(uri)
|
|
626
661
|
|
|
627
662
|
begin
|
|
663
|
+
# Net::HTTP#request never follows a redirect, which is the policy:
|
|
664
|
+
# the signed URL is the one destination the API host named, and a
|
|
665
|
+
# redirect from it is refused below, not dialled (SPEC §14 "Hop-2
|
|
666
|
+
# Redirect Policy"). Stated here so a move to a following client
|
|
667
|
+
# (Faraday, Net::HTTP.get_response's callers) has to argue with it.
|
|
628
668
|
response = http_client.request(request)
|
|
629
|
-
rescue StandardError
|
|
630
|
-
|
|
669
|
+
rescue StandardError
|
|
670
|
+
# SPEC §9: the transport error renders the signed URL, so neither its
|
|
671
|
+
# message nor the exception itself survives. cause: nil at the raise
|
|
672
|
+
# site — MRI sets the built-in cause at raise time past the class's
|
|
673
|
+
# stored cause: keyword (same pattern as oauth/exchange.rb).
|
|
674
|
+
raise NetworkError.new("Download failed"), cause: nil
|
|
675
|
+
end
|
|
676
|
+
|
|
677
|
+
# The exact set hop 1 dispatches on, not Net::HTTPRedirection — that
|
|
678
|
+
# class also covers 304, which is a cache answer, not a redirect.
|
|
679
|
+
if [ 301, 302, 303, 307, 308 ].include?(response.code.to_i)
|
|
680
|
+
raise ApiError.new("redirect #{response.code} on the signed download hop is not followed", http_status: response.code.to_i)
|
|
631
681
|
end
|
|
632
682
|
|
|
633
683
|
unless response.is_a?(Net::HTTPSuccess)
|
data/lib/basecamp/config.rb
CHANGED
|
@@ -35,7 +35,21 @@ module Basecamp
|
|
|
35
35
|
attr_accessor :max_jitter
|
|
36
36
|
|
|
37
37
|
# @return [Integer] maximum pages to fetch in paginated requests
|
|
38
|
-
|
|
38
|
+
attr_reader :max_pages
|
|
39
|
+
|
|
40
|
+
# A validating writer, not attr_accessor: Http reads the config live at
|
|
41
|
+
# every page boundary, so `config.max_pages = -1` after construction
|
|
42
|
+
# replaced the validated cap with a value validate! would have refused —
|
|
43
|
+
# the same second door TypeScript's compile-time-only `readonly` left
|
|
44
|
+
# open. The config deliberately stays mutable (from_file → load_from_env
|
|
45
|
+
# re-validation is builder-style by design); only the cap's invariant
|
|
46
|
+
# must hold at assignment.
|
|
47
|
+
#
|
|
48
|
+
# @param value [Integer] maximum pages to fetch in paginated requests
|
|
49
|
+
def max_pages=(value)
|
|
50
|
+
validate_max_pages!(value)
|
|
51
|
+
@max_pages = value
|
|
52
|
+
end
|
|
39
53
|
|
|
40
54
|
# Default values
|
|
41
55
|
DEFAULT_BASE_URL = "https://3.basecampapi.com"
|
|
@@ -164,7 +178,9 @@ module Basecamp
|
|
|
164
178
|
# @param path [String] path to JSON config file
|
|
165
179
|
# @return [Config]
|
|
166
180
|
def self.from_file(path)
|
|
167
|
-
|
|
181
|
+
# UTF-8 regardless of process locale — JSON is UTF-8 (RFC 8259), and
|
|
182
|
+
# LC_ALL=C would otherwise read as US-ASCII.
|
|
183
|
+
data = JSON.parse(File.read(path, encoding: "UTF-8"))
|
|
168
184
|
config = new(
|
|
169
185
|
base_url: data["base_url"] || DEFAULT_BASE_URL,
|
|
170
186
|
timeout: data["timeout"] || DEFAULT_TIMEOUT,
|
|
@@ -199,7 +215,13 @@ module Basecamp
|
|
|
199
215
|
def validate!
|
|
200
216
|
raise ArgumentError, "timeout must be positive" unless @timeout.is_a?(Numeric) && @timeout > 0
|
|
201
217
|
raise ArgumentError, "max_retries must be non-negative" unless @max_retries.is_a?(Integer) && @max_retries >= 0
|
|
202
|
-
|
|
218
|
+
validate_max_pages!(@max_pages)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Shared by validate! and the max_pages= writer. Integer-only, so a
|
|
222
|
+
# boolean or Float::INFINITY is refused along with zero and negatives.
|
|
223
|
+
def validate_max_pages!(value)
|
|
224
|
+
raise ArgumentError, "max_pages must be positive" unless value.is_a?(Integer) && value > 0
|
|
203
225
|
end
|
|
204
226
|
|
|
205
227
|
def normalize_url(url)
|
|
@@ -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-09-03T02:27:35Z",
|
|
5
5
|
"operations": {
|
|
6
6
|
"GetAccount": {
|
|
7
7
|
"retry": {
|
|
@@ -1576,6 +1576,17 @@
|
|
|
1576
1576
|
"maxPageSize": 50
|
|
1577
1577
|
}
|
|
1578
1578
|
},
|
|
1579
|
+
"ListRecentProjects": {
|
|
1580
|
+
"retry": {
|
|
1581
|
+
"maxAttempts": 3,
|
|
1582
|
+
"baseDelayMs": 1000,
|
|
1583
|
+
"backoff": "exponential",
|
|
1584
|
+
"retryOn": [
|
|
1585
|
+
429,
|
|
1586
|
+
503
|
|
1587
|
+
]
|
|
1588
|
+
}
|
|
1589
|
+
},
|
|
1579
1590
|
"MarkAsRead": {
|
|
1580
1591
|
"retry": {
|
|
1581
1592
|
"maxAttempts": 2,
|
|
@@ -1806,6 +1817,20 @@
|
|
|
1806
1817
|
"natural": true
|
|
1807
1818
|
}
|
|
1808
1819
|
},
|
|
1820
|
+
"RecordProjectVisit": {
|
|
1821
|
+
"retry": {
|
|
1822
|
+
"maxAttempts": 3,
|
|
1823
|
+
"baseDelayMs": 1000,
|
|
1824
|
+
"backoff": "exponential",
|
|
1825
|
+
"retryOn": [
|
|
1826
|
+
429,
|
|
1827
|
+
503
|
|
1828
|
+
]
|
|
1829
|
+
},
|
|
1830
|
+
"idempotent": {
|
|
1831
|
+
"natural": true
|
|
1832
|
+
}
|
|
1833
|
+
},
|
|
1809
1834
|
"UnarchiveProject": {
|
|
1810
1835
|
"retry": {
|
|
1811
1836
|
"maxAttempts": 3,
|
|
@@ -2144,6 +2169,34 @@
|
|
|
2144
2169
|
]
|
|
2145
2170
|
}
|
|
2146
2171
|
},
|
|
2172
|
+
"CreateBubbleUp": {
|
|
2173
|
+
"retry": {
|
|
2174
|
+
"maxAttempts": 3,
|
|
2175
|
+
"baseDelayMs": 1000,
|
|
2176
|
+
"backoff": "exponential",
|
|
2177
|
+
"retryOn": [
|
|
2178
|
+
429,
|
|
2179
|
+
503
|
|
2180
|
+
]
|
|
2181
|
+
},
|
|
2182
|
+
"idempotent": {
|
|
2183
|
+
"natural": true
|
|
2184
|
+
}
|
|
2185
|
+
},
|
|
2186
|
+
"DeleteBubbleUp": {
|
|
2187
|
+
"retry": {
|
|
2188
|
+
"maxAttempts": 3,
|
|
2189
|
+
"baseDelayMs": 1000,
|
|
2190
|
+
"backoff": "exponential",
|
|
2191
|
+
"retryOn": [
|
|
2192
|
+
429,
|
|
2193
|
+
503
|
|
2194
|
+
]
|
|
2195
|
+
},
|
|
2196
|
+
"idempotent": {
|
|
2197
|
+
"natural": true
|
|
2198
|
+
}
|
|
2199
|
+
},
|
|
2147
2200
|
"SetClientVisibility": {
|
|
2148
2201
|
"retry": {
|
|
2149
2202
|
"maxAttempts": 3,
|
|
@@ -2228,6 +2281,34 @@
|
|
|
2228
2281
|
]
|
|
2229
2282
|
}
|
|
2230
2283
|
},
|
|
2284
|
+
"SpotlightRecording": {
|
|
2285
|
+
"retry": {
|
|
2286
|
+
"maxAttempts": 3,
|
|
2287
|
+
"baseDelayMs": 1000,
|
|
2288
|
+
"backoff": "exponential",
|
|
2289
|
+
"retryOn": [
|
|
2290
|
+
429,
|
|
2291
|
+
503
|
|
2292
|
+
]
|
|
2293
|
+
},
|
|
2294
|
+
"idempotent": {
|
|
2295
|
+
"natural": true
|
|
2296
|
+
}
|
|
2297
|
+
},
|
|
2298
|
+
"UnspotlightRecording": {
|
|
2299
|
+
"retry": {
|
|
2300
|
+
"maxAttempts": 3,
|
|
2301
|
+
"baseDelayMs": 1000,
|
|
2302
|
+
"backoff": "exponential",
|
|
2303
|
+
"retryOn": [
|
|
2304
|
+
429,
|
|
2305
|
+
503
|
|
2306
|
+
]
|
|
2307
|
+
},
|
|
2308
|
+
"idempotent": {
|
|
2309
|
+
"natural": true
|
|
2310
|
+
}
|
|
2311
|
+
},
|
|
2231
2312
|
"UnarchiveRecording": {
|
|
2232
2313
|
"retry": {
|
|
2233
2314
|
"maxAttempts": 3,
|
|
@@ -2668,6 +2749,39 @@
|
|
|
2668
2749
|
"natural": true
|
|
2669
2750
|
}
|
|
2670
2751
|
},
|
|
2752
|
+
"GetTemplateLibrary": {
|
|
2753
|
+
"retry": {
|
|
2754
|
+
"maxAttempts": 3,
|
|
2755
|
+
"baseDelayMs": 1000,
|
|
2756
|
+
"backoff": "exponential",
|
|
2757
|
+
"retryOn": [
|
|
2758
|
+
429,
|
|
2759
|
+
503
|
|
2760
|
+
]
|
|
2761
|
+
}
|
|
2762
|
+
},
|
|
2763
|
+
"CreateTemplateLibraryCopy": {
|
|
2764
|
+
"retry": {
|
|
2765
|
+
"maxAttempts": 2,
|
|
2766
|
+
"baseDelayMs": 1000,
|
|
2767
|
+
"backoff": "exponential",
|
|
2768
|
+
"retryOn": [
|
|
2769
|
+
429,
|
|
2770
|
+
503
|
|
2771
|
+
]
|
|
2772
|
+
}
|
|
2773
|
+
},
|
|
2774
|
+
"GetTemplateLibraryCopy": {
|
|
2775
|
+
"retry": {
|
|
2776
|
+
"maxAttempts": 3,
|
|
2777
|
+
"baseDelayMs": 1000,
|
|
2778
|
+
"backoff": "exponential",
|
|
2779
|
+
"retryOn": [
|
|
2780
|
+
429,
|
|
2781
|
+
503
|
|
2782
|
+
]
|
|
2783
|
+
}
|
|
2784
|
+
},
|
|
2671
2785
|
"ListTemplates": {
|
|
2672
2786
|
"retry": {
|
|
2673
2787
|
"maxAttempts": 3,
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Basecamp
|
|
4
|
+
module Services
|
|
5
|
+
# Service for BubbleUps operations
|
|
6
|
+
#
|
|
7
|
+
# @generated from OpenAPI spec
|
|
8
|
+
class BubbleUpsService < BaseService
|
|
9
|
+
|
|
10
|
+
# Bubble up a recording for the current user, resurfacing it in the current
|
|
11
|
+
# @param recording_id [Integer] recording id ID
|
|
12
|
+
# @param at [String, nil] Timing for the bubble-up. `"now"` bubbles up immediately; a scheduling
|
|
13
|
+
# keyword (`"today"`, `"tomorrow"`, `"weekend"`, `"next_week"`) or an ISO8601
|
|
14
|
+
# date (e.g. `"2026-09-10"`) schedules it to resurface later. bc3 requires a
|
|
15
|
+
# value — omitting `at` errors server-side (`Date.iso8601(nil)`) — so send
|
|
16
|
+
# `"now"` for the immediate case.
|
|
17
|
+
# @return [void]
|
|
18
|
+
def create_bubble_up(recording_id:, at: nil)
|
|
19
|
+
with_operation(service: "bubbleups", operation: "create_bubble_up", is_mutation: true, resource_id: recording_id) do
|
|
20
|
+
http_post("/recordings/#{recording_id}/bubble_up.json", body: compact_params(at: at))
|
|
21
|
+
nil
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Remove the current user's bubble-up from a recording (returns 204 No Content).
|
|
26
|
+
# @param recording_id [Integer] recording id ID
|
|
27
|
+
# @return [void]
|
|
28
|
+
def delete_bubble_up(recording_id:)
|
|
29
|
+
with_operation(service: "bubbleups", operation: "delete_bubble_up", is_mutation: true, resource_id: recording_id) do
|
|
30
|
+
http_delete("/recordings/#{recording_id}/bubble_up.json")
|
|
31
|
+
nil
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -7,6 +7,14 @@ module Basecamp
|
|
|
7
7
|
# @generated from OpenAPI spec
|
|
8
8
|
class ProjectsService < BaseService
|
|
9
9
|
|
|
10
|
+
# List the projects the current user has most recently visited, most recent visit first.
|
|
11
|
+
# @return [Array<Hash>] response data
|
|
12
|
+
def list_recent_projects()
|
|
13
|
+
with_operation(service: "projects", operation: "list_recent_projects", is_mutation: false) do
|
|
14
|
+
http_get("/my/recent_projects.json", operation: "ListRecentProjects").json
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
10
18
|
# List projects (active by default; optionally archived/trashed)
|
|
11
19
|
# @param status [String, nil] active|archived|trashed
|
|
12
20
|
# @param page [Integer, nil] Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.
|
|
@@ -61,6 +69,16 @@ module Basecamp
|
|
|
61
69
|
end
|
|
62
70
|
end
|
|
63
71
|
|
|
72
|
+
# Record that the current user visited a project, moving it to the front of ListRecentProjects (returns 204 No Content).
|
|
73
|
+
# @param project_id [Integer] project id ID
|
|
74
|
+
# @return [void]
|
|
75
|
+
def record_project_visit(project_id:)
|
|
76
|
+
with_operation(service: "projects", operation: "record_project_visit", is_mutation: true, project_id: project_id) do
|
|
77
|
+
http_post("/projects/#{project_id}/recent_visit.json")
|
|
78
|
+
nil
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
64
82
|
# Restore a project to active status from trash as well as from the archive (returns 204 No Content).
|
|
65
83
|
# @param project_id [Integer] project id ID
|
|
66
84
|
# @return [void]
|
|
@@ -23,6 +23,25 @@ module Basecamp
|
|
|
23
23
|
end
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
+
# Put a recording's card in the spotlight area on its project or template home page.
|
|
27
|
+
# @param recording_id [Integer] recording id ID
|
|
28
|
+
# @return [Hash] response data
|
|
29
|
+
def spotlight(recording_id:)
|
|
30
|
+
with_operation(service: "recordings", operation: "spotlight", is_mutation: true, resource_id: recording_id) do
|
|
31
|
+
http_post("/recordings/#{recording_id}/spotlight.json").json
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Remove a recording from the spotlight area.
|
|
36
|
+
# @param recording_id [Integer] recording id ID
|
|
37
|
+
# @return [void]
|
|
38
|
+
def unspotlight(recording_id:)
|
|
39
|
+
with_operation(service: "recordings", operation: "unspotlight", is_mutation: true, resource_id: recording_id) do
|
|
40
|
+
http_delete("/recordings/#{recording_id}/spotlight.json")
|
|
41
|
+
nil
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
26
45
|
# Unarchive a recording (restore to active status)
|
|
27
46
|
# @param recording_id [Integer] recording id ID
|
|
28
47
|
# @return [void]
|
|
@@ -7,6 +7,34 @@ module Basecamp
|
|
|
7
7
|
# @generated from OpenAPI spec
|
|
8
8
|
class TemplatesService < BaseService
|
|
9
9
|
|
|
10
|
+
# Get the account's to-do list template library
|
|
11
|
+
# @return [Hash] response data
|
|
12
|
+
def get_library()
|
|
13
|
+
with_operation(service: "templates", operation: "get_library", is_mutation: false) do
|
|
14
|
+
http_get("/template_library.json", operation: "GetTemplateLibrary").json
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Start copying a to-do list template into a project
|
|
19
|
+
# @param template_recording_id [Integer] template recording id
|
|
20
|
+
# @param destination_parent_id [Integer] destination parent id
|
|
21
|
+
# @param adding_people_confirmed [Boolean, nil] Confirm granting destination-project access to people referenced by the template.
|
|
22
|
+
# @return [Hash] response data
|
|
23
|
+
def create_library_copy(template_recording_id:, destination_parent_id:, adding_people_confirmed: nil)
|
|
24
|
+
with_operation(service: "templates", operation: "create_library_copy", is_mutation: true) do
|
|
25
|
+
http_post("/template_library/copies.json", body: compact_params(template_recording_id: template_recording_id, destination_parent_id: destination_parent_id, adding_people_confirmed: adding_people_confirmed)).json
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Get the current status of a to-do list template copy
|
|
30
|
+
# @param copy_id [Integer] copy id ID
|
|
31
|
+
# @return [Hash] response data
|
|
32
|
+
def get_library_copy(copy_id:)
|
|
33
|
+
with_operation(service: "templates", operation: "get_library_copy", is_mutation: false, resource_id: copy_id) do
|
|
34
|
+
http_get("/template_library/copies/#{copy_id}", operation: "GetTemplateLibraryCopy").json
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
10
38
|
# List all templates visible to the current user
|
|
11
39
|
# @param status [String, nil] active|archived|trashed
|
|
12
40
|
# @param page [Integer, nil] Page number for paginating through results. Defaults to 1. A positive value selects exactly that page, not a starting offset; see SPEC section 8.
|