basecamp-sdk 0.9.0 → 0.10.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/lib/basecamp/client.rb +5 -0
- data/lib/basecamp/generated/metadata.json +290 -12
- data/lib/basecamp/generated/services/automation_service.rb +1 -1
- data/lib/basecamp/generated/services/cards_service.rb +2 -2
- data/lib/basecamp/generated/services/everything_service.rb +179 -0
- data/lib/basecamp/generated/services/message_types_service.rb +20 -15
- data/lib/basecamp/generated/services/my_assignments_service.rb +2 -2
- data/lib/basecamp/generated/services/my_notifications_service.rb +16 -2
- data/lib/basecamp/generated/services/people_service.rb +1 -1
- data/lib/basecamp/generated/services/recordings_service.rb +1 -1
- data/lib/basecamp/generated/services/schedules_service.rb +9 -1
- data/lib/basecamp/generated/services/timesheets_service.rb +1 -1
- data/lib/basecamp/generated/services/tools_service.rb +3 -2
- data/lib/basecamp/generated/types.rb +407 -18
- data/lib/basecamp/http.rb +2 -1
- data/lib/basecamp/services/cards_extensions.rb +67 -0
- data/lib/basecamp/version.rb +2 -2
- data/lib/basecamp.rb +5 -0
- data/scripts/generate-services.rb +15 -0
- data/scripts/generate-types.rb +29 -2
- metadata +4 -2
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Basecamp
|
|
4
|
+
module Services
|
|
5
|
+
# Merge-safe +update+ for cards, prepended onto the generated
|
|
6
|
+
# {CardsService} (see the +on_load+ hook in +basecamp.rb+).
|
|
7
|
+
#
|
|
8
|
+
# BC3 builds the card's update params as
|
|
9
|
+
# <tt>{ due_on: nil }.merge(card_params)</tt>
|
|
10
|
+
# (+kanban/cards_controller.rb+), so *any* update whose body omits +due_on+
|
|
11
|
+
# erases the card's due date. A sparse PUT — the natural thing to write —
|
|
12
|
+
# is therefore destructive on the raw endpoint, which remains available as
|
|
13
|
+
# {#update_verbatim}.
|
|
14
|
+
#
|
|
15
|
+
# +update+ composes the public +get+ and +update_verbatim+ methods, so
|
|
16
|
+
# hooks observe the two wire operations, not a synthetic composite.
|
|
17
|
+
#
|
|
18
|
+
# Not atomic: a concurrent due-date change landing between the GET and the
|
|
19
|
+
# PUT is overwritten with the value this call read. The window is one
|
|
20
|
+
# round-trip.
|
|
21
|
+
module CardsExtensions
|
|
22
|
+
# Updates a card without disturbing fields the caller did not mention.
|
|
23
|
+
#
|
|
24
|
+
# +due_on+ is tri-state, which is what makes this safe:
|
|
25
|
+
#
|
|
26
|
+
# * +nil+ (omitted) — the current due date is fetched and resent
|
|
27
|
+
# * <tt>""</tt> — the due date is cleared
|
|
28
|
+
# * a date — the due date is set
|
|
29
|
+
#
|
|
30
|
+
# The extra GET is only paid for in the +nil+ case, the one where the
|
|
31
|
+
# API would otherwise destroy something.
|
|
32
|
+
#
|
|
33
|
+
# Assignees are never resent on the caller's behalf: BC3 filters incoming
|
|
34
|
+
# IDs through +reachable_people+, so echoing back an id belonging to
|
|
35
|
+
# someone who has since lost board access would silently unassign them.
|
|
36
|
+
#
|
|
37
|
+
# @param card_id [Integer] card id
|
|
38
|
+
# @param title [String, nil] new title (nil = keep current)
|
|
39
|
+
# @param content [String, nil] new content (nil = keep current, "" clears)
|
|
40
|
+
# @param due_on [String, nil] new due date (nil = keep current, "" clears)
|
|
41
|
+
# @param assignee_ids [Array, nil] new assignees (nil = keep current, [] clears)
|
|
42
|
+
# @return [Hash] the updated card
|
|
43
|
+
def update(card_id:, title: nil, content: nil, due_on: nil, assignee_ids: nil)
|
|
44
|
+
resolved_due_on =
|
|
45
|
+
if due_on.nil?
|
|
46
|
+
get(card_id: card_id)["due_on"]
|
|
47
|
+
elsif due_on.to_s.empty?
|
|
48
|
+
# Clearing is encoded by OMITTING due_on — compact_params strips the
|
|
49
|
+
# nil below, and BC3 nils an omitted due date. Sending an explicit
|
|
50
|
+
# null would violate body compaction (SPEC §18), and sending ""
|
|
51
|
+
# risks a date-format error.
|
|
52
|
+
nil
|
|
53
|
+
else
|
|
54
|
+
due_on
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
update_verbatim(
|
|
58
|
+
card_id: card_id,
|
|
59
|
+
title: title,
|
|
60
|
+
content: content,
|
|
61
|
+
due_on: resolved_due_on,
|
|
62
|
+
assignee_ids: assignee_ids
|
|
63
|
+
)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
data/lib/basecamp/version.rb
CHANGED
data/lib/basecamp.rb
CHANGED
|
@@ -10,6 +10,11 @@ loader.collapse("#{__dir__}/basecamp/generated")
|
|
|
10
10
|
loader.on_load("Basecamp::Services::TodosService") do |klass, _abspath|
|
|
11
11
|
klass.prepend(Basecamp::Services::TodosExtensions)
|
|
12
12
|
end
|
|
13
|
+
# Same shape for cards: the generated class owns the constant and the
|
|
14
|
+
# merge-safe update is prepended over the generated update_verbatim.
|
|
15
|
+
loader.on_load("Basecamp::Services::CardsService") do |klass, _abspath|
|
|
16
|
+
klass.prepend(Basecamp::Services::CardsExtensions)
|
|
17
|
+
end
|
|
13
18
|
loader.setup
|
|
14
19
|
|
|
15
20
|
# Load generated types if available
|
|
@@ -146,6 +146,9 @@ class ServiceGenerator
|
|
|
146
146
|
'RepositionCardStep' => 'reposition',
|
|
147
147
|
'CreateCardStep' => 'create',
|
|
148
148
|
'UpdateCardStep' => 'update',
|
|
149
|
+
# The plain `update` name belongs to the merge-safe composite; the raw
|
|
150
|
+
# single-PUT path keeps a name that says what it does. See #467.
|
|
151
|
+
'UpdateCard' => 'update_verbatim',
|
|
149
152
|
'SetCardStepCompletion' => 'set_completion',
|
|
150
153
|
'GetQuestionnaire' => 'get_questionnaire',
|
|
151
154
|
'GetQuestion' => 'get_question',
|
|
@@ -424,6 +427,13 @@ class ServiceGenerator
|
|
|
424
427
|
response_schema = success_response&.dig('content', 'application/json', 'schema')
|
|
425
428
|
returns_void = response_schema.nil?
|
|
426
429
|
returns_array = response_schema&.dig('type') == 'array'
|
|
430
|
+
# Bare-array responses are usually a $ref to a single-member output alias, so
|
|
431
|
+
# the inline `type` is absent. Resolve the ref to detect them — used only for
|
|
432
|
+
# the YARD @return doc (an unpaginated bare array returns an Array, not a
|
|
433
|
+
# Hash); the pagination/body logic deliberately keys off the raw `type` so a
|
|
434
|
+
# bare array without x-basecamp-pagination stays a single request.
|
|
435
|
+
resolved_response = response_schema && response_schema['$ref'] ? resolve_schema_ref(response_schema) : response_schema
|
|
436
|
+
returns_bare_array = resolved_response&.dig('type') == 'array'
|
|
427
437
|
|
|
428
438
|
{
|
|
429
439
|
operation_id: operation_id,
|
|
@@ -440,6 +450,7 @@ class ServiceGenerator
|
|
|
440
450
|
multipart_field: multipart_field,
|
|
441
451
|
returns_void: returns_void,
|
|
442
452
|
returns_array: returns_array,
|
|
453
|
+
returns_bare_array: returns_bare_array,
|
|
443
454
|
is_mutation: http_method != 'GET',
|
|
444
455
|
has_pagination: !!operation['x-basecamp-pagination'],
|
|
445
456
|
pagination_key: operation.dig('x-basecamp-pagination', 'key')
|
|
@@ -658,6 +669,10 @@ class ServiceGenerator
|
|
|
658
669
|
lines << ' # @return [Hash] response data'
|
|
659
670
|
elsif is_paginated
|
|
660
671
|
lines << ' # @return [Enumerator<Hash>] paginated results'
|
|
672
|
+
elsif op[:returns_bare_array]
|
|
673
|
+
# Unpaginated bare array (single request, no Link-following) — e.g. the
|
|
674
|
+
# overdue todo/card feeds. Returns the parsed JSON array, not a Hash.
|
|
675
|
+
lines << ' # @return [Array<Hash>] response data'
|
|
661
676
|
else
|
|
662
677
|
lines << ' # @return [Hash] response data'
|
|
663
678
|
end
|
data/scripts/generate-types.rb
CHANGED
|
@@ -87,6 +87,33 @@ def generate_helpers
|
|
|
87
87
|
HELPERS
|
|
88
88
|
end
|
|
89
89
|
|
|
90
|
+
# Renders a deprecation +reason+ as one or more Ruby comment lines (#406).
|
|
91
|
+
#
|
|
92
|
+
# A reason can be sourced from a multi-line OpenAPI description. Interpolating
|
|
93
|
+
# one into a single "# @deprecated ..." line would leave every continuation on a
|
|
94
|
+
# bare, un-commented source line, which is a syntax error in the generated
|
|
95
|
+
# types.rb — Ruby is the only owned generator exposed this way (Python escapes
|
|
96
|
+
# the reason, Kotlin/TypeScript emit block comments). Splitting on line
|
|
97
|
+
# boundaries and prefixing each line with the comment leader keeps the output
|
|
98
|
+
# valid for any reason.
|
|
99
|
+
#
|
|
100
|
+
# +indent+ is the leading source indentation; +tag_prefix+ is inserted between
|
|
101
|
+
# the "# " leader and the YARD tag (the per-attribute site nests its tag under
|
|
102
|
+
# an @!attribute directive). Continuation lines are indented two columns past
|
|
103
|
+
# the tag so YARD folds them into the same tag's text.
|
|
104
|
+
#
|
|
105
|
+
# Single-line reasons render byte-identically to the previous single-line
|
|
106
|
+
# interpolation, so this is output-neutral for the current spec.
|
|
107
|
+
def deprecation_doc_lines(reason, indent:, tag_prefix: '')
|
|
108
|
+
lines = reason.to_s.split(/\r?\n/, -1)
|
|
109
|
+
out = [ "#{indent}# #{tag_prefix}@deprecated #{lines.first}" ]
|
|
110
|
+
continuation = "#{indent}# #{tag_prefix} "
|
|
111
|
+
lines.drop(1).each do |line|
|
|
112
|
+
out << (line.empty? ? "#{indent}#" : "#{continuation}#{line}")
|
|
113
|
+
end
|
|
114
|
+
out
|
|
115
|
+
end
|
|
116
|
+
|
|
90
117
|
# Main execution
|
|
91
118
|
if __FILE__ == $PROGRAM_NAME
|
|
92
119
|
openapi_path = ARGV[0] || File.expand_path('../../openapi.json', __dir__)
|
|
@@ -128,7 +155,7 @@ if __FILE__ == $PROGRAM_NAME
|
|
|
128
155
|
# not individual params, so a class-level @deprecated tag documents a wholly
|
|
129
156
|
# deprecated type.
|
|
130
157
|
if schema['deprecated']
|
|
131
|
-
puts
|
|
158
|
+
puts deprecation_doc_lines(schema['x-deprecated-reason'] || 'deprecated', indent: ' ')
|
|
132
159
|
end
|
|
133
160
|
puts " class #{name}"
|
|
134
161
|
puts ' include TypeHelpers'
|
|
@@ -147,7 +174,7 @@ if __FILE__ == $PROGRAM_NAME
|
|
|
147
174
|
|
|
148
175
|
ruby_name = k.gsub(/([A-Z])/, '_\1').downcase.gsub(/^_/, '')
|
|
149
176
|
puts " # @!attribute [rw] #{ruby_name}"
|
|
150
|
-
puts
|
|
177
|
+
puts deprecation_doc_lines(ps['x-deprecated-reason'] || 'deprecated', indent: ' ', tag_prefix: ' ')
|
|
151
178
|
end
|
|
152
179
|
|
|
153
180
|
puts " attr_accessor #{attr_names.map { |n| ":#{n}" }.join(", ")}"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: basecamp-sdk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.10.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Basecamp
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-07-
|
|
11
|
+
date: 2026-07-29 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: faraday
|
|
@@ -196,6 +196,7 @@ files:
|
|
|
196
196
|
- lib/basecamp/generated/services/comments_service.rb
|
|
197
197
|
- lib/basecamp/generated/services/documents_service.rb
|
|
198
198
|
- lib/basecamp/generated/services/events_service.rb
|
|
199
|
+
- lib/basecamp/generated/services/everything_service.rb
|
|
199
200
|
- lib/basecamp/generated/services/forwards_service.rb
|
|
200
201
|
- lib/basecamp/generated/services/gauges_service.rb
|
|
201
202
|
- lib/basecamp/generated/services/hill_charts_service.rb
|
|
@@ -253,6 +254,7 @@ files:
|
|
|
253
254
|
- lib/basecamp/request_result.rb
|
|
254
255
|
- lib/basecamp/security.rb
|
|
255
256
|
- lib/basecamp/services/authorization_service.rb
|
|
257
|
+
- lib/basecamp/services/cards_extensions.rb
|
|
256
258
|
- lib/basecamp/services/todos_extensions.rb
|
|
257
259
|
- lib/basecamp/static_token_provider.rb
|
|
258
260
|
- lib/basecamp/token_provider.rb
|