basecamp-sdk 0.6.0 → 0.7.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: da0e2d03c80b5801b886da56a59535f99cb521bd8233a146720ee117f76954b8
4
- data.tar.gz: 4a3ea1075d88cac04a7dcc796f3c21d235aaffbb9d036be088321746c435c012
3
+ metadata.gz: 67da09be280f8e08a9639f77ea7d08f70e5327609cac6a46812bf76069a09a18
4
+ data.tar.gz: 98f22496c4a83e17e076e2339848ee6c1738b4f5382889114acb053ea010ffe1
5
5
  SHA512:
6
- metadata.gz: e5d5298402d8239d525cff7aeac7815254cb7a1a3a967ed6f29fe9fea581432f0c2e7b272edc52a866b457ec988afc11f3ff6dbb48977b5e2ac716af8bbdb46a
7
- data.tar.gz: f37ba99c63d4fb9836fe9fc5e6e717e0166f97892069da141f2be746ac95c8e15629319fb1deb4c23cdd4a7ee30d1ac84a83bf183e19930c9d394e47995704e6
6
+ metadata.gz: 27cae3068542c51e3a02a994deaf8b68d6a91e8ddae6d5ce1ac7b98123718e5aa884879ee5ee679b432b30074d5f1c00d62ca2bd714ca144a2d3b352b398c961
7
+ data.tar.gz: 8da2274e9fcaa49289e5e6c0587dd396f2f56012146fa490decef7fb195be3223cd34f23c1ba3e227a6538b9620473ac98dbf856aeabfa845de59ff0bc984d1a
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+
3
4
  module Basecamp
4
5
  # Main client for the Basecamp API.
5
6
  #
@@ -188,6 +189,16 @@ module Basecamp
188
189
  @parent.http.post_raw(account_path(path), body: body, content_type: content_type)
189
190
  end
190
191
 
192
+ # Performs a PUT request with raw binary data scoped to this account.
193
+ # Used for multipart uploads (e.g., account logo).
194
+ # @param path [String] URL path (without account prefix)
195
+ # @param body [String, IO] raw binary data
196
+ # @param content_type [String] MIME content type
197
+ # @return [Response]
198
+ def put_raw(path, body:, content_type:)
199
+ @parent.http.put_raw(account_path(path), body: body, content_type: content_type)
200
+ end
201
+
191
202
  # Fetches all pages of a paginated resource.
192
203
  # @param path [String] URL path (without account prefix)
193
204
  # @param params [Hash] query parameters
@@ -320,6 +331,11 @@ module Basecamp
320
331
  service(:todosets) { Services::TodosetsService.new(self) }
321
332
  end
322
333
 
334
+ # @return [Services::HillChartsService]
335
+ def hill_charts
336
+ service(:hill_charts) { Services::HillChartsService.new(self) }
337
+ end
338
+
323
339
  # @return [Services::TodolistsService]
324
340
  def todolists
325
341
  service(:todolists) { Services::TodolistsService.new(self) }
@@ -500,6 +516,26 @@ module Basecamp
500
516
  service(:boosts) { Services::BoostsService.new(self) }
501
517
  end
502
518
 
519
+ # @return [Services::AccountService]
520
+ def account
521
+ service(:account) { Services::AccountService.new(self) }
522
+ end
523
+
524
+ # @return [Services::GaugesService]
525
+ def gauges
526
+ service(:gauges) { Services::GaugesService.new(self) }
527
+ end
528
+
529
+ # @return [Services::MyAssignmentsService]
530
+ def my_assignments
531
+ service(:my_assignments) { Services::MyAssignmentsService.new(self) }
532
+ end
533
+
534
+ # @return [Services::MyNotificationsService]
535
+ def my_notifications
536
+ service(:my_notifications) { Services::MyNotificationsService.new(self) }
537
+ end
538
+
503
539
  # @!endgroup
504
540
 
505
541
  private
@@ -1,8 +1,47 @@
1
1
  {
2
2
  "$schema": "https://basecamp.com/schemas/sdk-metadata.json",
3
3
  "version": "1.0.0",
4
- "generated": "2026-03-16T01:24:22Z",
4
+ "generated": "2026-03-23T21:15:35Z",
5
5
  "operations": {
6
+ "GetAccount": {
7
+ "retry": {
8
+ "maxAttempts": 3,
9
+ "baseDelayMs": 1000,
10
+ "backoff": "exponential",
11
+ "retryOn": [
12
+ 429,
13
+ 503
14
+ ]
15
+ }
16
+ },
17
+ "RemoveAccountLogo": {
18
+ "retry": {
19
+ "maxAttempts": 2,
20
+ "baseDelayMs": 1000,
21
+ "backoff": "exponential",
22
+ "retryOn": [
23
+ 429,
24
+ 503
25
+ ]
26
+ },
27
+ "idempotent": {
28
+ "natural": true
29
+ }
30
+ },
31
+ "UpdateAccountName": {
32
+ "retry": {
33
+ "maxAttempts": 2,
34
+ "baseDelayMs": 1000,
35
+ "backoff": "exponential",
36
+ "retryOn": [
37
+ 429,
38
+ 503
39
+ ]
40
+ },
41
+ "idempotent": {
42
+ "natural": true
43
+ }
44
+ },
6
45
  "CreateAttachment": {
7
46
  "retry": {
8
47
  "maxAttempts": 3,
@@ -750,6 +789,45 @@
750
789
  "natural": true
751
790
  }
752
791
  },
792
+ "GetGaugeNeedle": {
793
+ "retry": {
794
+ "maxAttempts": 3,
795
+ "baseDelayMs": 1000,
796
+ "backoff": "exponential",
797
+ "retryOn": [
798
+ 429,
799
+ 503
800
+ ]
801
+ }
802
+ },
803
+ "UpdateGaugeNeedle": {
804
+ "retry": {
805
+ "maxAttempts": 2,
806
+ "baseDelayMs": 1000,
807
+ "backoff": "exponential",
808
+ "retryOn": [
809
+ 429,
810
+ 503
811
+ ]
812
+ },
813
+ "idempotent": {
814
+ "natural": true
815
+ }
816
+ },
817
+ "DestroyGaugeNeedle": {
818
+ "retry": {
819
+ "maxAttempts": 2,
820
+ "baseDelayMs": 1000,
821
+ "backoff": "exponential",
822
+ "retryOn": [
823
+ 429,
824
+ 503
825
+ ]
826
+ },
827
+ "idempotent": {
828
+ "natural": true
829
+ }
830
+ },
753
831
  "GetForward": {
754
832
  "retry": {
755
833
  "maxAttempts": 3,
@@ -826,6 +904,17 @@
826
904
  "maxPageSize": 50
827
905
  }
828
906
  },
907
+ "ListLineupMarkers": {
908
+ "retry": {
909
+ "maxAttempts": 3,
910
+ "baseDelayMs": 1000,
911
+ "backoff": "exponential",
912
+ "retryOn": [
913
+ 429,
914
+ 503
915
+ ]
916
+ }
917
+ },
829
918
  "CreateLineupMarker": {
830
919
  "retry": {
831
920
  "maxAttempts": 2,
@@ -928,6 +1017,64 @@
928
1017
  "natural": true
929
1018
  }
930
1019
  },
1020
+ "GetMyAssignments": {
1021
+ "retry": {
1022
+ "maxAttempts": 3,
1023
+ "baseDelayMs": 1000,
1024
+ "backoff": "exponential",
1025
+ "retryOn": [
1026
+ 429,
1027
+ 503
1028
+ ]
1029
+ }
1030
+ },
1031
+ "GetMyCompletedAssignments": {
1032
+ "retry": {
1033
+ "maxAttempts": 3,
1034
+ "baseDelayMs": 1000,
1035
+ "backoff": "exponential",
1036
+ "retryOn": [
1037
+ 429,
1038
+ 503
1039
+ ]
1040
+ }
1041
+ },
1042
+ "GetMyDueAssignments": {
1043
+ "retry": {
1044
+ "maxAttempts": 3,
1045
+ "baseDelayMs": 1000,
1046
+ "backoff": "exponential",
1047
+ "retryOn": [
1048
+ 429,
1049
+ 503
1050
+ ]
1051
+ }
1052
+ },
1053
+ "GetMyPreferences": {
1054
+ "retry": {
1055
+ "maxAttempts": 3,
1056
+ "baseDelayMs": 1000,
1057
+ "backoff": "exponential",
1058
+ "retryOn": [
1059
+ 429,
1060
+ 503
1061
+ ]
1062
+ }
1063
+ },
1064
+ "UpdateMyPreferences": {
1065
+ "retry": {
1066
+ "maxAttempts": 2,
1067
+ "baseDelayMs": 1000,
1068
+ "backoff": "exponential",
1069
+ "retryOn": [
1070
+ 429,
1071
+ 503
1072
+ ]
1073
+ },
1074
+ "idempotent": {
1075
+ "natural": true
1076
+ }
1077
+ },
931
1078
  "GetMyProfile": {
932
1079
  "retry": {
933
1080
  "maxAttempts": 3,
@@ -939,6 +1086,20 @@
939
1086
  ]
940
1087
  }
941
1088
  },
1089
+ "UpdateMyProfile": {
1090
+ "retry": {
1091
+ "maxAttempts": 2,
1092
+ "baseDelayMs": 1000,
1093
+ "backoff": "exponential",
1094
+ "retryOn": [
1095
+ 429,
1096
+ 503
1097
+ ]
1098
+ },
1099
+ "idempotent": {
1100
+ "natural": true
1101
+ }
1102
+ },
942
1103
  "GetQuestionReminders": {
943
1104
  "retry": {
944
1105
  "maxAttempts": 3,
@@ -954,6 +1115,31 @@
954
1115
  "maxPageSize": 50
955
1116
  }
956
1117
  },
1118
+ "GetMyNotifications": {
1119
+ "retry": {
1120
+ "maxAttempts": 3,
1121
+ "baseDelayMs": 1000,
1122
+ "backoff": "exponential",
1123
+ "retryOn": [
1124
+ 429,
1125
+ 503
1126
+ ]
1127
+ }
1128
+ },
1129
+ "MarkAsRead": {
1130
+ "retry": {
1131
+ "maxAttempts": 2,
1132
+ "baseDelayMs": 1000,
1133
+ "backoff": "exponential",
1134
+ "retryOn": [
1135
+ 429,
1136
+ 503
1137
+ ]
1138
+ },
1139
+ "idempotent": {
1140
+ "natural": true
1141
+ }
1142
+ },
957
1143
  "ListPeople": {
958
1144
  "retry": {
959
1145
  "maxAttempts": 3,
@@ -981,6 +1167,42 @@
981
1167
  ]
982
1168
  }
983
1169
  },
1170
+ "GetOutOfOffice": {
1171
+ "retry": {
1172
+ "maxAttempts": 3,
1173
+ "baseDelayMs": 1000,
1174
+ "backoff": "exponential",
1175
+ "retryOn": [
1176
+ 429,
1177
+ 503
1178
+ ]
1179
+ }
1180
+ },
1181
+ "EnableOutOfOffice": {
1182
+ "retry": {
1183
+ "maxAttempts": 2,
1184
+ "baseDelayMs": 1000,
1185
+ "backoff": "exponential",
1186
+ "retryOn": [
1187
+ 429,
1188
+ 503
1189
+ ]
1190
+ }
1191
+ },
1192
+ "DisableOutOfOffice": {
1193
+ "retry": {
1194
+ "maxAttempts": 2,
1195
+ "baseDelayMs": 1000,
1196
+ "backoff": "exponential",
1197
+ "retryOn": [
1198
+ 429,
1199
+ 503
1200
+ ]
1201
+ },
1202
+ "idempotent": {
1203
+ "natural": true
1204
+ }
1205
+ },
984
1206
  "ListProjects": {
985
1207
  "retry": {
986
1208
  "maxAttempts": 3,
@@ -1063,6 +1285,47 @@
1063
1285
  "natural": true
1064
1286
  }
1065
1287
  },
1288
+ "ToggleGauge": {
1289
+ "retry": {
1290
+ "maxAttempts": 2,
1291
+ "baseDelayMs": 1000,
1292
+ "backoff": "exponential",
1293
+ "retryOn": [
1294
+ 429,
1295
+ 503
1296
+ ]
1297
+ },
1298
+ "idempotent": {
1299
+ "natural": true
1300
+ }
1301
+ },
1302
+ "ListGaugeNeedles": {
1303
+ "retry": {
1304
+ "maxAttempts": 3,
1305
+ "baseDelayMs": 1000,
1306
+ "backoff": "exponential",
1307
+ "retryOn": [
1308
+ 429,
1309
+ 503
1310
+ ]
1311
+ },
1312
+ "pagination": {
1313
+ "style": "link",
1314
+ "totalCountHeader": "X-Total-Count",
1315
+ "maxPageSize": 50
1316
+ }
1317
+ },
1318
+ "CreateGaugeNeedle": {
1319
+ "retry": {
1320
+ "maxAttempts": 2,
1321
+ "baseDelayMs": 1000,
1322
+ "backoff": "exponential",
1323
+ "retryOn": [
1324
+ 429,
1325
+ 503
1326
+ ]
1327
+ }
1328
+ },
1066
1329
  "ListProjectPeople": {
1067
1330
  "retry": {
1068
1331
  "maxAttempts": 3,
@@ -1617,6 +1880,22 @@
1617
1880
  "natural": true
1618
1881
  }
1619
1882
  },
1883
+ "ListGauges": {
1884
+ "retry": {
1885
+ "maxAttempts": 3,
1886
+ "baseDelayMs": 1000,
1887
+ "backoff": "exponential",
1888
+ "retryOn": [
1889
+ 429,
1890
+ 503
1891
+ ]
1892
+ },
1893
+ "pagination": {
1894
+ "style": "link",
1895
+ "totalCountHeader": "X-Total-Count",
1896
+ "maxPageSize": 50
1897
+ }
1898
+ },
1620
1899
  "GetProgressReport": {
1621
1900
  "retry": {
1622
1901
  "maxAttempts": 3,
@@ -2117,6 +2396,31 @@
2117
2396
  ]
2118
2397
  }
2119
2398
  },
2399
+ "GetHillChart": {
2400
+ "retry": {
2401
+ "maxAttempts": 3,
2402
+ "baseDelayMs": 1000,
2403
+ "backoff": "exponential",
2404
+ "retryOn": [
2405
+ 429,
2406
+ 503
2407
+ ]
2408
+ }
2409
+ },
2410
+ "UpdateHillChartSettings": {
2411
+ "retry": {
2412
+ "maxAttempts": 3,
2413
+ "baseDelayMs": 1000,
2414
+ "backoff": "exponential",
2415
+ "retryOn": [
2416
+ 429,
2417
+ 503
2418
+ ]
2419
+ },
2420
+ "idempotent": {
2421
+ "natural": true
2422
+ }
2423
+ },
2120
2424
  "ListTodolists": {
2121
2425
  "retry": {
2122
2426
  "maxAttempts": 3,
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Basecamp
4
+ module Services
5
+ # Service for Account operations
6
+ #
7
+ # @generated from OpenAPI spec
8
+ class AccountService < BaseService
9
+
10
+ # Get the account for the current access token
11
+ # @return [Hash] response data
12
+ def get_account()
13
+ with_operation(service: "account", operation: "get_account", is_mutation: false) do
14
+ http_get("/account.json").json
15
+ end
16
+ end
17
+
18
+ # Upload or replace the account logo.
19
+ # @param io [IO, String] File data to upload (IO object or string)
20
+ # @param filename [String] Display name for the uploaded file
21
+ # @param content_type [String] MIME type of the file (e.g., "image/png")
22
+ # @return [void]
23
+ def update_account_logo(io:, filename:, content_type:)
24
+ with_operation(service: "account", operation: "update_account_logo", is_mutation: true) do
25
+ http_put_multipart("/account/logo.json", io: io, filename: filename, content_type: content_type, field: "logo")
26
+ nil
27
+ end
28
+ end
29
+
30
+ # Remove the account logo. Only administrators and account owners can use this endpoint.
31
+ # @return [void]
32
+ def remove_account_logo()
33
+ with_operation(service: "account", operation: "remove_account_logo", is_mutation: true) do
34
+ http_delete("/account/logo.json")
35
+ nil
36
+ end
37
+ end
38
+
39
+ # Rename the current account. Only account owners can use this endpoint.
40
+ # @param name [String] name
41
+ # @return [Hash] response data
42
+ def update_account_name(name:)
43
+ with_operation(service: "account", operation: "update_account_name", is_mutation: true) do
44
+ http_put("/account/name.json", body: compact_params(name: name)).json
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "cgi/escape"
4
+ require "securerandom"
4
5
 
5
6
  module Basecamp
6
7
  module Services
@@ -162,12 +163,43 @@ module Basecamp
162
163
  # @see AccountClient#post_raw
163
164
  # @!method paginate(path, params: {}, &block)
164
165
  # @see AccountClient#paginate
165
- %i[get post put delete post_raw].each do |method|
166
+ %i[get post put delete post_raw put_raw].each do |method|
166
167
  define_method(:"http_#{method}") do |*args, **kwargs, &block|
167
168
  @client.public_send(method, *args, **kwargs, &block)
168
169
  end
169
170
  end
170
171
 
172
+ # Upload a file as multipart/form-data.
173
+ # @param path [String] API path
174
+ # @param io [IO, String] file data
175
+ # @param filename [String] display filename
176
+ # @param content_type [String] MIME type
177
+ # @param field [String] form field name
178
+ def http_put_multipart(path, io:, filename:, content_type:, field: "file")
179
+ boundary = "BasecampSDK#{SecureRandom.hex(16)}"
180
+ body = build_multipart_body(boundary: boundary, field: field, io: io, \
181
+ filename: filename, content_type: content_type)
182
+ http_put_raw(path, body: body, content_type: "multipart/form-data; boundary=#{boundary}")
183
+ nil
184
+ end
185
+
186
+ private
187
+
188
+ def build_multipart_body(boundary:, field:, io:, filename:, content_type:)
189
+ data = io.respond_to?(:read) ? io.read : io.to_s
190
+ safe_filename = filename.tr("\r\n", "").gsub("\\", "\\\\").gsub('"', '\\"')
191
+ safe_content_type = content_type.tr("\r\n", "")
192
+ body = "".b
193
+ body << "--#{boundary}\r\n"
194
+ body << "Content-Disposition: form-data; name=\"#{field}\"; filename=\"#{safe_filename}\"\r\n"
195
+ body << "Content-Type: #{safe_content_type}\r\n"
196
+ body << "\r\n"
197
+ body << data
198
+ body << "\r\n"
199
+ body << "--#{boundary}--\r\n"
200
+ body.force_encoding(Encoding::BINARY)
201
+ end
202
+
171
203
  # Paginate doesn't conflict with service methods, keep as-is
172
204
  def paginate(...)
173
205
  @client.paginate(...)
@@ -81,10 +81,13 @@ module Basecamp
81
81
 
82
82
  # List all lines (messages) in a campfire
83
83
  # @param campfire_id [Integer] campfire id ID
84
+ # @param sort [String, nil] created_at|updated_at
85
+ # @param direction [String, nil] asc|desc
84
86
  # @return [Enumerator<Hash>] paginated results
85
- def list_lines(campfire_id:)
87
+ def list_lines(campfire_id:, sort: nil, direction: nil)
86
88
  wrap_paginated(service: "campfires", operation: "list_lines", is_mutation: false, resource_id: campfire_id) do
87
- paginate("/chats/#{campfire_id}/lines.json")
89
+ params = compact_params(sort: sort, direction: direction)
90
+ paginate("/chats/#{campfire_id}/lines.json", params: params)
88
91
  end
89
92
  end
90
93
 
@@ -122,10 +125,13 @@ module Basecamp
122
125
 
123
126
  # List uploaded files in a campfire
124
127
  # @param campfire_id [Integer] campfire id ID
128
+ # @param sort [String, nil] created_at|updated_at
129
+ # @param direction [String, nil] asc|desc
125
130
  # @return [Enumerator<Hash>] paginated results
126
- def list_uploads(campfire_id:)
131
+ def list_uploads(campfire_id:, sort: nil, direction: nil)
127
132
  wrap_paginated(service: "campfires", operation: "list_uploads", is_mutation: false, resource_id: campfire_id) do
128
- paginate("/chats/#{campfire_id}/uploads.json")
133
+ params = compact_params(sort: sort, direction: direction)
134
+ paginate("/chats/#{campfire_id}/uploads.json", params: params)
129
135
  end
130
136
  end
131
137
 
@@ -32,10 +32,11 @@ module Basecamp
32
32
  # Move a card to a different column
33
33
  # @param card_id [Integer] card id ID
34
34
  # @param column_id [Integer] column id
35
+ # @param position [Integer, nil] 1-indexed position within the destination column. Defaults to 1 (top).
35
36
  # @return [void]
36
- def move(card_id:, column_id:)
37
+ def move(card_id:, column_id:, position: nil)
37
38
  with_operation(service: "cards", operation: "move", is_mutation: true, resource_id: card_id) do
38
- http_post("/card_tables/cards/#{card_id}/moves.json", body: compact_params(column_id: column_id))
39
+ http_post("/card_tables/cards/#{card_id}/moves.json", body: compact_params(column_id: column_id, position: position))
39
40
  nil
40
41
  end
41
42
  end
@@ -8,10 +8,13 @@ module Basecamp
8
8
  class ClientApprovalsService < BaseService
9
9
 
10
10
  # List all client approvals in a project
11
+ # @param sort [String, nil] created_at|updated_at
12
+ # @param direction [String, nil] asc|desc
11
13
  # @return [Enumerator<Hash>] paginated results
12
- def list()
14
+ def list(sort: nil, direction: nil)
13
15
  wrap_paginated(service: "clientapprovals", operation: "list", is_mutation: false) do
14
- paginate("/client/approvals.json")
16
+ params = compact_params(sort: sort, direction: direction)
17
+ paginate("/client/approvals.json", params: params)
15
18
  end
16
19
  end
17
20
 
@@ -8,10 +8,13 @@ module Basecamp
8
8
  class ClientCorrespondencesService < BaseService
9
9
 
10
10
  # List all client correspondences in a project
11
+ # @param sort [String, nil] created_at|updated_at
12
+ # @param direction [String, nil] asc|desc
11
13
  # @return [Enumerator<Hash>] paginated results
12
- def list()
14
+ def list(sort: nil, direction: nil)
13
15
  wrap_paginated(service: "clientcorrespondences", operation: "list", is_mutation: false) do
14
- paginate("/client/correspondences.json")
16
+ params = compact_params(sort: sort, direction: direction)
17
+ paginate("/client/correspondences.json", params: params)
15
18
  end
16
19
  end
17
20
 
@@ -56,10 +56,13 @@ module Basecamp
56
56
 
57
57
  # List all forwards in an inbox
58
58
  # @param inbox_id [Integer] inbox id ID
59
+ # @param sort [String, nil] created_at|updated_at
60
+ # @param direction [String, nil] asc|desc
59
61
  # @return [Enumerator<Hash>] paginated results
60
- def list(inbox_id:)
62
+ def list(inbox_id:, sort: nil, direction: nil)
61
63
  wrap_paginated(service: "forwards", operation: "list", is_mutation: false, resource_id: inbox_id) do
62
- paginate("/inboxes/#{inbox_id}/forwards.json")
64
+ params = compact_params(sort: sort, direction: direction)
65
+ paginate("/inboxes/#{inbox_id}/forwards.json", params: params)
63
66
  end
64
67
  end
65
68
  end