From 93f13479b480f49f2925c3ec1e02009abdea5d5d Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Fri, 24 Jul 2026 23:01:47 -0700 Subject: [PATCH 1/5] Absorb the everything-aggregates flat family (bc3 #11627, PR-5 of 2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BC5's account-wide recording listings (everything/*_controller.rb, documented by bc3 #11627 in doc/api/sections/everything.md) had no SDK surface. Add a new EverythingService with the 8 flat-family operations, mirroring the flat aggregate pattern of GetMyAssignments: - Six recency-ordered, Link-paginated roots: GetEverythingMessages/Comments/ Checkins/Forwards (element = the generic Recording projection the wire actually returns, embedding bucket), GetEverythingBoosts (element = Boost, with its booster + nested recording), and GetEverythingFiles. - Two unpaginated, oldest-due-date-first arrays: GetEverythingOverdueTodos (Todo) and GetEverythingOverdueCards (Card), modeled as plain full arrays (single-member output, no pagination → bare array via smithy-bare-arrays) and tested with no Link-following. - /files.json is heterogeneous (Upload + Document + attachment-envelope): modeled as the optional-field superset EverythingFile with the kind and repeatable people_ids[] filters. Width/height get the nullable *FlexInt Go enhancement (and Kotlin needed EverythingFile added to the generator TYPE_ALIASES so it emits a typed model instead of JsonElement). Runtime decode proof for the heterogeneous feed in every SDK (Go, TS, Ruby, Python, Kotlin, Swift): a non-empty test decoding all three file variants in one array. Go wrappers add multi-page Link-following (paginated roots) and plain full-array decode (overdue); Go tests cover multi-page pagination, unpaginated-with-Link-header-ignored, per-variant files decode, and the kind/people_ids[] filters. Service auto-derives from the Everything tag; client wiring added where hand-written (Go accessor, TS defineService, Ruby method, Python sync+async properties; Kotlin/Swift auto-wired). everything-aggregates.md moves to partial-coverage with a two-phase absorption note; it flips to absorbed-in-sdk only after PR-5b models the bucket-grouped family. Conformance paths.json + per-runner dispatch and per-group canary entries are tracked as follow-ups landing with PR-5b. --- behavior-model.json | 120 + conformance/runner/go/main.go | 32 + conformance/runner/python/runner.py | 16 + conformance/runner/ruby/runner.rb | 16 + .../runner/typescript/live-dispatch.ts | 34 + conformance/runner/typescript/runner.test.ts | 32 + conformance/tests/live-my-surface.json | 423 ++- conformance/tests/paths.json | 714 ++++- go/pkg/basecamp/client.go | 11 + go/pkg/basecamp/everything.go | 465 +++ go/pkg/basecamp/everything_test.go | 206 ++ go/pkg/basecamp/url-routes.json | 104 + go/pkg/generated/client.gen.go | 2807 +++++++++++++---- .../com/basecamp/sdk/conformance/Main.kt | 40 + .../com/basecamp/sdk/generator/Config.kt | 1 + .../com/basecamp/sdk/generated/Metadata.kt | 8 + .../sdk/generated/ServiceAccessors.kt | 4 + .../sdk/generated/models/EverythingFile.kt | 48 + .../basecamp/sdk/generated/services/Types.kt | 50 + .../sdk/generated/services/everything.kt | 192 ++ .../basecamp/sdk/EverythingFilesDecodeTest.kt | 103 + openapi.json | 983 +++++- python/scripts/generate_services.py | 8 +- python/src/basecamp/async_client.py | 6 + python/src/basecamp/client.py | 6 + python/src/basecamp/generated/metadata.json | 88 + .../basecamp/generated/services/__init__.py | 3 + .../basecamp/generated/services/automation.py | 10 +- .../basecamp/generated/services/everything.py | 126 + .../generated/services/my_assignments.py | 20 +- .../src/basecamp/generated/services/people.py | 10 +- .../basecamp/generated/services/timesheets.py | 12 +- python/src/basecamp/generated/types.py | 33 + python/tests/services/test_everything.py | 352 +++ ruby/lib/basecamp/client.rb | 5 + ruby/lib/basecamp/generated/metadata.json | 120 +- .../generated/services/everything_service.rb | 89 + ruby/lib/basecamp/generated/types.rb | 80 +- .../services/everything_service_test.rb | 326 ++ scripts/enhance-openapi-go-types.sh | 32 + spec/api-gaps/README.md | 2 +- spec/api-gaps/everything-aggregates.md | 73 +- spec/basecamp.smithy | 291 ++ spec/overlays/tags.smithy | 10 + .../Generated/AccountClient+Services.swift | 1 + .../Sources/Basecamp/Generated/Metadata.swift | 8 + .../Generated/Models/EverythingFile.swift | 35 + .../Services/EverythingService.swift | 184 ++ .../BasecampTests/GeneratedServiceTests.swift | 90 + typescript/scripts/generate-services.ts | 2 + typescript/src/client.ts | 4 + typescript/src/generated/metadata.ts | 120 +- .../src/generated/openapi-stripped.json | 953 +++++- typescript/src/generated/path-mapping.ts | 8 + typescript/src/generated/schema.d.ts | 737 ++++- typescript/src/generated/services/boosts.ts | 22 +- .../src/generated/services/everything.ts | 304 ++ typescript/src/generated/services/index.ts | 1 + typescript/src/index.ts | 5 + typescript/tests/services/everything.test.ts | 334 ++ 60 files changed, 9953 insertions(+), 966 deletions(-) create mode 100644 go/pkg/basecamp/everything.go create mode 100644 go/pkg/basecamp/everything_test.go create mode 100644 kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/EverythingFile.kt create mode 100644 kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/services/everything.kt create mode 100644 kotlin/sdk/src/commonTest/kotlin/com/basecamp/sdk/EverythingFilesDecodeTest.kt create mode 100644 python/src/basecamp/generated/services/everything.py create mode 100644 python/tests/services/test_everything.py create mode 100644 ruby/lib/basecamp/generated/services/everything_service.rb create mode 100644 ruby/test/basecamp/services/everything_service_test.rb create mode 100644 swift/Sources/Basecamp/Generated/Models/EverythingFile.swift create mode 100644 swift/Sources/Basecamp/Generated/Services/EverythingService.swift create mode 100644 typescript/src/generated/services/everything.ts create mode 100644 typescript/tests/services/everything.test.ts diff --git a/behavior-model.json b/behavior-model.json index 5fe249fe0..0b537c17e 100644 --- a/behavior-model.json +++ b/behavior-model.json @@ -781,6 +781,126 @@ ] } }, + "GetEverythingBoosts": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetEverythingCheckins": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetEverythingComments": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetEverythingFiles": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetEverythingForwards": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetEverythingMessages": { + "readonly": true, + "pagination": { + "style": "link", + "maxPageSize": 50 + }, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetEverythingOverdueCards": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, + "GetEverythingOverdueTodos": { + "readonly": true, + "retry": { + "max": 3, + "base_delay_ms": 1000, + "backoff": "exponential", + "retry_on": [ + 429, + 503 + ] + } + }, "GetForward": { "readonly": true, "retry": { diff --git a/conformance/runner/go/main.go b/conformance/runner/go/main.go index 9d7399ec7..100bfcc0f 100644 --- a/conformance/runner/go/main.go +++ b/conformance/runner/go/main.go @@ -682,6 +682,38 @@ func executeOperation(ctx context.Context, account *basecamp.AccountClient, tc T } return operationResult{err: nil} + case "GetEverythingMessages": + _, err := account.Everything().Messages(ctx, 0) + return operationResult{err: err} + + case "GetEverythingComments": + _, err := account.Everything().Comments(ctx, 0) + return operationResult{err: err} + + case "GetEverythingCheckins": + _, err := account.Everything().Checkins(ctx, 0) + return operationResult{err: err} + + case "GetEverythingForwards": + _, err := account.Everything().Forwards(ctx, 0) + return operationResult{err: err} + + case "GetEverythingBoosts": + _, err := account.Everything().Boosts(ctx, 0) + return operationResult{err: err} + + case "GetEverythingFiles": + _, err := account.Everything().Files(ctx, 0, nil) + return operationResult{err: err} + + case "GetEverythingOverdueTodos": + _, err := account.Everything().OverdueTodos(ctx) + return operationResult{err: err} + + case "GetEverythingOverdueCards": + _, err := account.Everything().OverdueCards(ctx) + return operationResult{err: err} + default: return operationResult{ err: fmt.Errorf("unknown operation: %s", tc.Operation), diff --git a/conformance/runner/python/runner.py b/conformance/runner/python/runner.py index fd101b162..a9f028f35 100644 --- a/conformance/runner/python/runner.py +++ b/conformance/runner/python/runner.py @@ -150,6 +150,22 @@ def __call__(self, operation: str, *, path_params: dict, query_params: dict, bod return self._account.tools.enable(tool_id=path_params["toolId"]) case "UploadsDownload": return self._account.uploads.download(upload_id=path_params["uploadId"]) + case "GetEverythingMessages": + return self._account.everything.get_everything_messages() + case "GetEverythingComments": + return self._account.everything.get_everything_comments() + case "GetEverythingCheckins": + return self._account.everything.get_everything_checkins() + case "GetEverythingForwards": + return self._account.everything.get_everything_forwards() + case "GetEverythingBoosts": + return self._account.everything.get_everything_boosts() + case "GetEverythingFiles": + return self._account.everything.get_everything_files() + case "GetEverythingOverdueTodos": + return self._account.everything.get_everything_overdue_todos() + case "GetEverythingOverdueCards": + return self._account.everything.get_everything_overdue_cards() case _: raise ValueError(f"Unknown operation: {operation}") diff --git a/conformance/runner/ruby/runner.rb b/conformance/runner/ruby/runner.rb index 1010b6b96..669a6b29f 100644 --- a/conformance/runner/ruby/runner.rb +++ b/conformance/runner/ruby/runner.rb @@ -165,6 +165,22 @@ def call(operation, path_params: {}, query_params: {}, body: nil, path: "") todo_id: path_params["todoId"], **todo_write_kwargs(body) ) + when "GetEverythingMessages" + @account.everything.get_everything_messages.to_a + when "GetEverythingComments" + @account.everything.get_everything_comments.to_a + when "GetEverythingCheckins" + @account.everything.get_everything_checkins.to_a + when "GetEverythingForwards" + @account.everything.get_everything_forwards.to_a + when "GetEverythingBoosts" + @account.everything.get_everything_boosts.to_a + when "GetEverythingFiles" + @account.everything.get_everything_files.to_a + when "GetEverythingOverdueTodos" + @account.everything.get_everything_overdue_todos + when "GetEverythingOverdueCards" + @account.everything.get_everything_overdue_cards else raise "Unknown operation: #{operation}" end diff --git a/conformance/runner/typescript/live-dispatch.ts b/conformance/runner/typescript/live-dispatch.ts index 44ce29538..0238f5990 100644 --- a/conformance/runner/typescript/live-dispatch.ts +++ b/conformance/runner/typescript/live-dispatch.ts @@ -112,6 +112,40 @@ export const LIVE_OPERATIONS: Record = { }, }, + // Everything aggregates — flat family (one canary per group). + GetEverythingMessages: { + fixtures: [], + call: async (ctx) => ({ resolvedIds: {}, result: await ctx.client.everything.everythingMessages() }), + }, + GetEverythingComments: { + fixtures: [], + call: async (ctx) => ({ resolvedIds: {}, result: await ctx.client.everything.everythingComments() }), + }, + GetEverythingCheckins: { + fixtures: [], + call: async (ctx) => ({ resolvedIds: {}, result: await ctx.client.everything.everythingCheckins() }), + }, + GetEverythingForwards: { + fixtures: [], + call: async (ctx) => ({ resolvedIds: {}, result: await ctx.client.everything.everythingForwards() }), + }, + GetEverythingBoosts: { + fixtures: [], + call: async (ctx) => ({ resolvedIds: {}, result: await ctx.client.everything.everythingBoosts() }), + }, + GetEverythingFiles: { + fixtures: [], + call: async (ctx) => ({ resolvedIds: {}, result: await ctx.client.everything.everythingFiles() }), + }, + GetEverythingOverdueTodos: { + fixtures: [], + call: async (ctx) => ({ resolvedIds: {}, result: await ctx.client.everything.everythingOverdueTodos() }), + }, + GetEverythingOverdueCards: { + fixtures: [], + call: async (ctx) => ({ resolvedIds: {}, result: await ctx.client.everything.everythingOverdueCards() }), + }, + GetMyProfile: { fixtures: [], call: async (ctx) => { diff --git a/conformance/runner/typescript/runner.test.ts b/conformance/runner/typescript/runner.test.ts index a743f4e01..2b54c1766 100644 --- a/conformance/runner/typescript/runner.test.ts +++ b/conformance/runner/typescript/runner.test.ts @@ -260,6 +260,38 @@ async function executeOperation( await client.reports.personProgress(Number(params.personId)); break; + case "GetEverythingMessages": + await client.everything.everythingMessages(); + break; + + case "GetEverythingComments": + await client.everything.everythingComments(); + break; + + case "GetEverythingCheckins": + await client.everything.everythingCheckins(); + break; + + case "GetEverythingForwards": + await client.everything.everythingForwards(); + break; + + case "GetEverythingBoosts": + await client.everything.everythingBoosts(); + break; + + case "GetEverythingFiles": + await client.everything.everythingFiles(); + break; + + case "GetEverythingOverdueTodos": + await client.everything.everythingOverdueTodos(); + break; + + case "GetEverythingOverdueCards": + await client.everything.everythingOverdueCards(); + break; + case "GetTool": await client.tools.get(Number(params.toolId)); break; diff --git a/conformance/tests/live-my-surface.json b/conformance/tests/live-my-surface.json index 6625fd6de..0918d0df2 100644 --- a/conformance/tests/live-my-surface.json +++ b/conformance/tests/live-my-surface.json @@ -6,10 +6,17 @@ "operation": "GetProgressReport", "method": "GET", "path": "/reports/progress.json", - "tags": ["live", "read-only"], + "tags": [ + "live", + "read-only" + ], "liveAssertions": [ - { "type": "liveCallSucceeds" }, - { "type": "liveSchemaValidate" } + { + "type": "liveCallSucceeds" + }, + { + "type": "liveSchemaValidate" + } ] }, { @@ -19,10 +26,17 @@ "operation": "GetBubbleUps", "method": "GET", "path": "/my/readings/bubble_ups.json", - "tags": ["live", "read-only"], + "tags": [ + "live", + "read-only" + ], "liveAssertions": [ - { "type": "liveCallSucceeds" }, - { "type": "liveSchemaValidate" } + { + "type": "liveCallSucceeds" + }, + { + "type": "liveSchemaValidate" + } ] }, { @@ -32,10 +46,17 @@ "operation": "ListProjects", "method": "GET", "path": "/projects.json", - "tags": ["live", "read-only"], + "tags": [ + "live", + "read-only" + ], "liveAssertions": [ - { "type": "liveCallSucceeds" }, - { "type": "liveSchemaValidate" } + { + "type": "liveCallSucceeds" + }, + { + "type": "liveSchemaValidate" + } ] }, { @@ -45,29 +66,55 @@ "operation": "GetProject", "method": "GET", "path": "/projects/${PROJECT_ID}.json", - "fixtureIds": { "PROJECT_ID": "PROJECT_ID" }, - "pathParams": { "projectId": "${PROJECT_ID}" }, - "tags": ["live", "read-only"], + "fixtureIds": { + "PROJECT_ID": "PROJECT_ID" + }, + "pathParams": { + "projectId": "${PROJECT_ID}" + }, + "tags": [ + "live", + "read-only" + ], "liveAssertions": [ - { "type": "liveCallSucceeds" }, - { "type": "liveSchemaValidate" }, + { + "type": "liveCallSucceeds" + }, + { + "type": "liveSchemaValidate" + }, { "type": "liveResponseFieldsRequired", - "fields": ["id", "status", "created_at", "updated_at", "name", "url", "app_url"] + "fields": [ + "id", + "status", + "created_at", + "updated_at", + "name", + "url", + "app_url" + ] } ] }, { "mode": "live", "name": "GetMyAssignments decodes priorities + non_priorities", - "description": "Verifies MyAssignment.bucket JSON-key compat — the field is preserved despite route renames.", + "description": "Verifies MyAssignment.bucket JSON-key compat \u2014 the field is preserved despite route renames.", "operation": "GetMyAssignments", "method": "GET", "path": "/my/assignments.json", - "tags": ["live", "read-only"], + "tags": [ + "live", + "read-only" + ], "liveAssertions": [ - { "type": "liveCallSucceeds" }, - { "type": "liveSchemaValidate" } + { + "type": "liveCallSucceeds" + }, + { + "type": "liveSchemaValidate" + } ] }, { @@ -76,10 +123,17 @@ "operation": "GetMyCompletedAssignments", "method": "GET", "path": "/my/assignments/completed.json", - "tags": ["live", "read-only"], + "tags": [ + "live", + "read-only" + ], "liveAssertions": [ - { "type": "liveCallSucceeds" }, - { "type": "liveSchemaValidate" } + { + "type": "liveCallSucceeds" + }, + { + "type": "liveSchemaValidate" + } ] }, { @@ -88,23 +142,38 @@ "operation": "GetMyDueAssignments", "method": "GET", "path": "/my/assignments/due.json", - "tags": ["live", "read-only"], + "tags": [ + "live", + "read-only" + ], "liveAssertions": [ - { "type": "liveCallSucceeds" }, - { "type": "liveSchemaValidate" } + { + "type": "liveCallSucceeds" + }, + { + "type": "liveSchemaValidate" + } ] }, { "mode": "live", "name": "GetMyNotifications decodes unreads/reads/memories/bubble_ups", - "description": "Asserts the call succeeds and the raw body validates against the GetMyNotifications response schema on production BC5 (bubble_ups / scheduled_bubble_ups / memories are all schema-checked as arrays). It does not assert memories is empty — BC5 ships memories as an always-empty placeholder by documented contract (doc/api/sections/my_notifications.md, bc3 #11628), noted here as context; see spec/api-gaps/memories-emptied-regression.md.", + "description": "Asserts the call succeeds and the raw body validates against the GetMyNotifications response schema on production BC5 (bubble_ups / scheduled_bubble_ups / memories are all schema-checked as arrays). It does not assert memories is empty \u2014 BC5 ships memories as an always-empty placeholder by documented contract (doc/api/sections/my_notifications.md, bc3 #11628), noted here as context; see spec/api-gaps/memories-emptied-regression.md.", "operation": "GetMyNotifications", "method": "GET", "path": "/my/readings.json", - "tags": ["live", "read-only", "bc5-additive"], + "tags": [ + "live", + "read-only", + "bc5-additive" + ], "liveAssertions": [ - { "type": "liveCallSucceeds" }, - { "type": "liveSchemaValidate" } + { + "type": "liveCallSucceeds" + }, + { + "type": "liveSchemaValidate" + } ] }, { @@ -114,29 +183,52 @@ "operation": "GetMyProfile", "method": "GET", "path": "/my/profile.json", - "tags": ["live", "read-only", "bc5-additive"], + "tags": [ + "live", + "read-only", + "bc5-additive" + ], "liveAssertions": [ - { "type": "liveCallSucceeds" }, - { "type": "liveSchemaValidate" }, + { + "type": "liveCallSucceeds" + }, + { + "type": "liveSchemaValidate" + }, { "type": "liveResponseFieldsRequired", - "fields": ["id", "name"] + "fields": [ + "id", + "name" + ] } ] }, { "mode": "live", "name": "GetTodoset decodes the BC5 count + url additions", - "description": "Verifies todos_count, completed_loose_todos_count, todos_url, app_todos_url decode (when present — eligibility-gated like other BC5 additions).", + "description": "Verifies todos_count, completed_loose_todos_count, todos_url, app_todos_url decode (when present \u2014 eligibility-gated like other BC5 additions).", "operation": "GetTodoset", "method": "GET", "path": "/todosets/${TODOSET_ID}.json", - "fixtureIds": { "TODOSET_ID": "TODOSET_ID" }, - "pathParams": { "todosetId": "${TODOSET_ID}" }, - "tags": ["live", "read-only", "bc5-additive"], + "fixtureIds": { + "TODOSET_ID": "TODOSET_ID" + }, + "pathParams": { + "todosetId": "${TODOSET_ID}" + }, + "tags": [ + "live", + "read-only", + "bc5-additive" + ], "liveAssertions": [ - { "type": "liveCallSucceeds" }, - { "type": "liveSchemaValidate" } + { + "type": "liveCallSucceeds" + }, + { + "type": "liveSchemaValidate" + } ] }, { @@ -146,12 +238,23 @@ "operation": "ListTodolists", "method": "GET", "path": "/todosets/${TODOSET_ID}/todolists.json", - "fixtureIds": { "TODOSET_ID": "TODOSET_ID" }, - "pathParams": { "todosetId": "${TODOSET_ID}" }, - "tags": ["live", "read-only"], + "fixtureIds": { + "TODOSET_ID": "TODOSET_ID" + }, + "pathParams": { + "todosetId": "${TODOSET_ID}" + }, + "tags": [ + "live", + "read-only" + ], "liveAssertions": [ - { "type": "liveCallSucceeds" }, - { "type": "liveSchemaValidate" } + { + "type": "liveCallSucceeds" + }, + { + "type": "liveSchemaValidate" + } ] }, { @@ -161,26 +264,52 @@ "operation": "ListTodos", "method": "GET", "path": "/todolists/${TODOLIST_ID}/todos.json", - "fixtureIds": { "TODOLIST_ID": "TODOLIST_ID" }, - "pathParams": { "todolistId": "${TODOLIST_ID}" }, - "tags": ["live", "read-only", "bc5-additive"], + "fixtureIds": { + "TODOLIST_ID": "TODOLIST_ID" + }, + "pathParams": { + "todolistId": "${TODOLIST_ID}" + }, + "tags": [ + "live", + "read-only", + "bc5-additive" + ], "liveAssertions": [ - { "type": "liveCallSucceeds" }, - { "type": "liveSchemaValidate" } + { + "type": "liveCallSucceeds" + }, + { + "type": "liveSchemaValidate" + } ] }, { "mode": "live", "name": "Search accepts the BC5 filter params (type_names[]/since) and decodes results", - "description": "ACCEPTANCE/DECODING coverage for the absorbed search filter surface (spec/api-gaps/search-filter-additions.md, bc3 #12361). Drives search with the new type_names[] array + since params through the SDK's bracketed wire encoding (the dispatch call in live-dispatch.ts is authoritative). New params are silently ignored on BC4, accepted on BC5. HONESTY NOTE: the available live assertions prove only 2xx + SearchResult schema validity — NOT that BC5 actually filters by the params. No result-membership assertion exists in the vocabulary (conformance/schema.json), so this canary is decoding/acceptance coverage, not proof the filter is respected.", + "description": "ACCEPTANCE/DECODING coverage for the absorbed search filter surface (spec/api-gaps/search-filter-additions.md, bc3 #12361). Drives search with the new type_names[] array + since params through the SDK's bracketed wire encoding (the dispatch call in live-dispatch.ts is authoritative). New params are silently ignored on BC4, accepted on BC5. HONESTY NOTE: the available live assertions prove only 2xx + SearchResult schema validity \u2014 NOT that BC5 actually filters by the params. No result-membership assertion exists in the vocabulary (conformance/schema.json), so this canary is decoding/acceptance coverage, not proof the filter is respected.", "operation": "Search", "method": "GET", "path": "/search.json", - "queryParams": { "q": "the", "type_names[]": ["Message"], "since": "last_30_days" }, - "tags": ["live", "read-only", "bc5-additive"], + "queryParams": { + "q": "the", + "type_names[]": [ + "Message" + ], + "since": "last_30_days" + }, + "tags": [ + "live", + "read-only", + "bc5-additive" + ], "liveAssertions": [ - { "type": "liveCallSucceeds" }, - { "type": "liveSchemaValidate" } + { + "type": "liveCallSucceeds" + }, + { + "type": "liveSchemaValidate" + } ] }, { @@ -190,11 +319,189 @@ "operation": "ListRecordings", "method": "GET", "path": "/projects/recordings.json", - "queryParams": { "type": "Door" }, - "tags": ["live", "read-only", "bc5-additive"], + "queryParams": { + "type": "Door" + }, + "tags": [ + "live", + "read-only", + "bc5-additive" + ], + "liveAssertions": [ + { + "type": "liveCallSucceeds" + }, + { + "type": "liveSchemaValidate" + } + ] + }, + { + "mode": "live", + "name": "GetEverythingMessages validates against the everything-aggregates schema", + "description": "DECODING coverage for the everything-aggregates flat family (spec/api-gaps/everything-aggregates.md, bc3 #11627). Exercises every message across all projects and validates each item against its schema. Live-dormant: validates statically until credentials are provisioned.", + "operation": "GetEverythingMessages", + "method": "GET", + "path": "/messages.json", + "tags": [ + "live", + "read-only", + "bc5-additive" + ], + "liveAssertions": [ + { + "type": "liveCallSucceeds" + }, + { + "type": "liveSchemaValidate" + } + ] + }, + { + "mode": "live", + "name": "GetEverythingComments validates against the everything-aggregates schema", + "description": "DECODING coverage for the everything-aggregates flat family (spec/api-gaps/everything-aggregates.md, bc3 #11627). Exercises every comment across all projects and validates each item against its schema. Live-dormant: validates statically until credentials are provisioned.", + "operation": "GetEverythingComments", + "method": "GET", + "path": "/comments.json", + "tags": [ + "live", + "read-only", + "bc5-additive" + ], + "liveAssertions": [ + { + "type": "liveCallSucceeds" + }, + { + "type": "liveSchemaValidate" + } + ] + }, + { + "mode": "live", + "name": "GetEverythingCheckins validates against the everything-aggregates schema", + "description": "DECODING coverage for the everything-aggregates flat family (spec/api-gaps/everything-aggregates.md, bc3 #11627). Exercises every check-in answer across all projects and validates each item against its schema. Live-dormant: validates statically until credentials are provisioned.", + "operation": "GetEverythingCheckins", + "method": "GET", + "path": "/checkins.json", + "tags": [ + "live", + "read-only", + "bc5-additive" + ], + "liveAssertions": [ + { + "type": "liveCallSucceeds" + }, + { + "type": "liveSchemaValidate" + } + ] + }, + { + "mode": "live", + "name": "GetEverythingForwards validates against the everything-aggregates schema", + "description": "DECODING coverage for the everything-aggregates flat family (spec/api-gaps/everything-aggregates.md, bc3 #11627). Exercises every inbox forward across all projects and validates each item against its schema. Live-dormant: validates statically until credentials are provisioned.", + "operation": "GetEverythingForwards", + "method": "GET", + "path": "/forwards.json", + "tags": [ + "live", + "read-only", + "bc5-additive" + ], + "liveAssertions": [ + { + "type": "liveCallSucceeds" + }, + { + "type": "liveSchemaValidate" + } + ] + }, + { + "mode": "live", + "name": "GetEverythingBoosts validates against the everything-aggregates schema", + "description": "DECODING coverage for the everything-aggregates flat family (spec/api-gaps/everything-aggregates.md, bc3 #11627). Exercises every boost across all projects and validates each item against its schema. Live-dormant: validates statically until credentials are provisioned.", + "operation": "GetEverythingBoosts", + "method": "GET", + "path": "/boosts.json", + "tags": [ + "live", + "read-only", + "bc5-additive" + ], + "liveAssertions": [ + { + "type": "liveCallSucceeds" + }, + { + "type": "liveSchemaValidate" + } + ] + }, + { + "mode": "live", + "name": "GetEverythingFiles validates against the everything-aggregates schema", + "description": "DECODING coverage for the everything-aggregates flat family (spec/api-gaps/everything-aggregates.md, bc3 #11627). Exercises the heterogeneous files feed (uploads/documents/attachments) and validates each item against its schema. Live-dormant: validates statically until credentials are provisioned.", + "operation": "GetEverythingFiles", + "method": "GET", + "path": "/files.json", + "tags": [ + "live", + "read-only", + "bc5-additive" + ], + "liveAssertions": [ + { + "type": "liveCallSucceeds" + }, + { + "type": "liveSchemaValidate" + } + ] + }, + { + "mode": "live", + "name": "GetEverythingOverdueTodos validates against the everything-aggregates schema", + "description": "DECODING coverage for the everything-aggregates flat family (spec/api-gaps/everything-aggregates.md, bc3 #11627). Exercises the unpaginated oldest-first overdue to-dos and validates each item against its schema. Live-dormant: validates statically until credentials are provisioned.", + "operation": "GetEverythingOverdueTodos", + "method": "GET", + "path": "/todos/overdue.json", + "tags": [ + "live", + "read-only", + "bc5-additive" + ], + "liveAssertions": [ + { + "type": "liveCallSucceeds" + }, + { + "type": "liveSchemaValidate" + } + ] + }, + { + "mode": "live", + "name": "GetEverythingOverdueCards validates against the everything-aggregates schema", + "description": "DECODING coverage for the everything-aggregates flat family (spec/api-gaps/everything-aggregates.md, bc3 #11627). Exercises the unpaginated oldest-first overdue cards and validates each item against its schema. Live-dormant: validates statically until credentials are provisioned.", + "operation": "GetEverythingOverdueCards", + "method": "GET", + "path": "/cards/overdue.json", + "tags": [ + "live", + "read-only", + "bc5-additive" + ], "liveAssertions": [ - { "type": "liveCallSucceeds" }, - { "type": "liveSchemaValidate" } + { + "type": "liveCallSucceeds" + }, + { + "type": "liveSchemaValidate" + } ] } ] diff --git a/conformance/tests/paths.json b/conformance/tests/paths.json index 6af0703cd..b03c033dc 100644 --- a/conformance/tests/paths.json +++ b/conformance/tests/paths.json @@ -5,15 +5,28 @@ "operation": "GetProjectTimeline", "method": "GET", "path": "/projects/{projectId}/timeline.json", - "pathParams": {"projectId": 12345}, + "pathParams": { + "projectId": 12345 + }, "mockResponses": [ - {"status": 200, "body": []} + { + "status": 200, + "body": [] + } ], "assertions": [ - {"type": "requestPath", "expected": "/999/projects/12345/timeline.json"}, - {"type": "noError"} + { + "type": "requestPath", + "expected": "/999/projects/12345/timeline.json" + }, + { + "type": "noError" + } ], - "tags": ["path", "timeline"] + "tags": [ + "path", + "timeline" + ] }, { "name": "Progress report uses /reports/ path", @@ -22,13 +35,24 @@ "method": "GET", "path": "/reports/progress.json", "mockResponses": [ - {"status": 200, "body": []} + { + "status": 200, + "body": [] + } ], "assertions": [ - {"type": "requestPath", "expected": "/999/reports/progress.json"}, - {"type": "noError"} + { + "type": "requestPath", + "expected": "/999/reports/progress.json" + }, + { + "type": "noError" + } ], - "tags": ["path", "reports"] + "tags": [ + "path", + "reports" + ] }, { "name": "Person progress uses /reports/users/ path with .json", @@ -36,15 +60,34 @@ "operation": "GetPersonProgress", "method": "GET", "path": "/reports/users/progress/{personId}.json", - "pathParams": {"personId": 45678}, + "pathParams": { + "personId": 45678 + }, "mockResponses": [ - {"status": 200, "body": {"person": {"id": 45678, "name": "Test"}, "events": []}} + { + "status": 200, + "body": { + "person": { + "id": 45678, + "name": "Test" + }, + "events": [] + } + } ], "assertions": [ - {"type": "requestPath", "expected": "/999/reports/users/progress/45678.json"}, - {"type": "noError"} + { + "type": "requestPath", + "expected": "/999/reports/users/progress/45678.json" + }, + { + "type": "noError" + } ], - "tags": ["path", "reports"] + "tags": [ + "path", + "reports" + ] }, { "name": "Project timesheet uses project-scoped /projects/{projectId}/timesheet.json path", @@ -52,15 +95,28 @@ "operation": "GetProjectTimesheet", "method": "GET", "path": "/projects/{projectId}/timesheet.json", - "pathParams": {"projectId": 12345}, + "pathParams": { + "projectId": 12345 + }, "mockResponses": [ - {"status": 200, "body": []} + { + "status": 200, + "body": [] + } ], "assertions": [ - {"type": "requestPath", "expected": "/999/projects/12345/timesheet.json"}, - {"type": "noError"} + { + "type": "requestPath", + "expected": "/999/projects/12345/timesheet.json" + }, + { + "type": "noError" + } ], - "tags": ["path", "timesheets"] + "tags": [ + "path", + "timesheets" + ] }, { "name": "Timesheet entry get uses /timesheet_entries/{entryId} path", @@ -68,15 +124,57 @@ "operation": "GetTimesheetEntry", "method": "GET", "path": "/timesheet_entries/{entryId}", - "pathParams": {"entryId": 999}, + "pathParams": { + "entryId": 999 + }, "mockResponses": [ - {"status": 200, "body": {"id": 999, "status": "active", "visible_to_clients": false, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", "title": "Timesheet entry", "inherits_status": true, "type": "Timesheet::Entry", "url": "https://3.basecampapi.com/999/buckets/456/timesheet_entries/999.json", "app_url": "https://3.basecamp.com/999/buckets/456/timesheet_entries/999", "parent": {"id": 2, "title": "Timesheet", "type": "Timesheet", "url": "https://3.basecampapi.com/999/buckets/456/timesheets/2.json", "app_url": "https://3.basecamp.com/999/buckets/456/timesheets/2"}, "bucket": {"id": 456, "name": "Test", "type": "Project"}, "creator": {"id": 1, "name": "Test User"}, "date": "2024-01-01", "hours": "4.0"}} + { + "status": 200, + "body": { + "id": 999, + "status": "active", + "visible_to_clients": false, + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z", + "title": "Timesheet entry", + "inherits_status": true, + "type": "Timesheet::Entry", + "url": "https://3.basecampapi.com/999/buckets/456/timesheet_entries/999.json", + "app_url": "https://3.basecamp.com/999/buckets/456/timesheet_entries/999", + "parent": { + "id": 2, + "title": "Timesheet", + "type": "Timesheet", + "url": "https://3.basecampapi.com/999/buckets/456/timesheets/2.json", + "app_url": "https://3.basecamp.com/999/buckets/456/timesheets/2" + }, + "bucket": { + "id": 456, + "name": "Test", + "type": "Project" + }, + "creator": { + "id": 1, + "name": "Test User" + }, + "date": "2024-01-01", + "hours": "4.0" + } + } ], "assertions": [ - {"type": "requestPath", "expected": "/999/timesheet_entries/999"}, - {"type": "noError"} + { + "type": "requestPath", + "expected": "/999/timesheet_entries/999" + }, + { + "type": "noError" + } ], - "tags": ["path", "timesheets"] + "tags": [ + "path", + "timesheets" + ] }, { "name": "Timesheet entry update uses /timesheet_entries/{entryId} path", @@ -84,16 +182,60 @@ "operation": "UpdateTimesheetEntry", "method": "PUT", "path": "/timesheet_entries/{entryId}", - "pathParams": {"entryId": 999}, - "requestBody": {"hours": "4.0"}, + "pathParams": { + "entryId": 999 + }, + "requestBody": { + "hours": "4.0" + }, "mockResponses": [ - {"status": 200, "body": {"id": 999, "status": "active", "visible_to_clients": false, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", "title": "Timesheet entry", "inherits_status": true, "type": "Timesheet::Entry", "url": "https://3.basecampapi.com/999/buckets/456/timesheet_entries/999.json", "app_url": "https://3.basecamp.com/999/buckets/456/timesheet_entries/999", "parent": {"id": 2, "title": "Timesheet", "type": "Timesheet", "url": "https://3.basecampapi.com/999/buckets/456/timesheets/2.json", "app_url": "https://3.basecamp.com/999/buckets/456/timesheets/2"}, "bucket": {"id": 456, "name": "Test", "type": "Project"}, "creator": {"id": 1, "name": "Test User"}, "date": "2024-01-01", "hours": "4.0"}} + { + "status": 200, + "body": { + "id": 999, + "status": "active", + "visible_to_clients": false, + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z", + "title": "Timesheet entry", + "inherits_status": true, + "type": "Timesheet::Entry", + "url": "https://3.basecampapi.com/999/buckets/456/timesheet_entries/999.json", + "app_url": "https://3.basecamp.com/999/buckets/456/timesheet_entries/999", + "parent": { + "id": 2, + "title": "Timesheet", + "type": "Timesheet", + "url": "https://3.basecampapi.com/999/buckets/456/timesheets/2.json", + "app_url": "https://3.basecamp.com/999/buckets/456/timesheets/2" + }, + "bucket": { + "id": 456, + "name": "Test", + "type": "Project" + }, + "creator": { + "id": 1, + "name": "Test User" + }, + "date": "2024-01-01", + "hours": "4.0" + } + } ], "assertions": [ - {"type": "requestPath", "expected": "/999/timesheet_entries/999"}, - {"type": "noError"} + { + "type": "requestPath", + "expected": "/999/timesheet_entries/999" + }, + { + "type": "noError" + } ], - "tags": ["path", "timesheets"] + "tags": [ + "path", + "timesheets" + ] }, { "name": "List webhooks uses bucket-scoped /buckets/{bucketId}/webhooks.json path", @@ -101,15 +243,28 @@ "operation": "ListWebhooks", "method": "GET", "path": "/buckets/{bucketId}/webhooks.json", - "pathParams": {"bucketId": 456}, + "pathParams": { + "bucketId": 456 + }, "mockResponses": [ - {"status": 200, "body": []} + { + "status": 200, + "body": [] + } ], "assertions": [ - {"type": "requestPath", "expected": "/999/buckets/456/webhooks.json"}, - {"type": "noError"} + { + "type": "requestPath", + "expected": "/999/buckets/456/webhooks.json" + }, + { + "type": "noError" + } ], - "tags": ["path", "webhooks"] + "tags": [ + "path", + "webhooks" + ] }, { "name": "Create webhook uses bucket-scoped /buckets/{bucketId}/webhooks.json path", @@ -117,16 +272,45 @@ "operation": "CreateWebhook", "method": "POST", "path": "/buckets/{bucketId}/webhooks.json", - "pathParams": {"bucketId": 456}, - "requestBody": {"payload_url": "https://example.com/hook", "types": ["Todo"]}, + "pathParams": { + "bucketId": 456 + }, + "requestBody": { + "payload_url": "https://example.com/hook", + "types": [ + "Todo" + ] + }, "mockResponses": [ - {"status": 201, "body": {"id": 1, "payload_url": "https://example.com/hook", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", "url": "https://3.basecampapi.com/999/buckets/456/webhooks/1.json", "app_url": "https://3.basecamp.com/999/buckets/456/webhooks/1", "active": true, "types": ["Todo"]}} + { + "status": 201, + "body": { + "id": 1, + "payload_url": "https://example.com/hook", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z", + "url": "https://3.basecampapi.com/999/buckets/456/webhooks/1.json", + "app_url": "https://3.basecamp.com/999/buckets/456/webhooks/1", + "active": true, + "types": [ + "Todo" + ] + } + } ], "assertions": [ - {"type": "requestPath", "expected": "/999/buckets/456/webhooks.json"}, - {"type": "noError"} + { + "type": "requestPath", + "expected": "/999/buckets/456/webhooks.json" + }, + { + "type": "noError" + } ], - "tags": ["path", "webhooks"] + "tags": [ + "path", + "webhooks" + ] }, { "name": "GetTool uses flat /dock/tools/{toolId} path (no bucketId)", @@ -134,15 +318,44 @@ "operation": "GetTool", "method": "GET", "path": "/dock/tools/{toolId}", - "pathParams": {"toolId": 789}, + "pathParams": { + "toolId": 789 + }, "mockResponses": [ - {"status": 200, "body": {"id": 789, "name": "todoset", "title": "To-dos", "status": "active", "enabled": true, "position": 1, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", "url": "https://3.basecampapi.com/999/buckets/456/todosets/789.json", "app_url": "https://3.basecamp.com/999/buckets/456/todosets/789", "bucket": {"id": 456, "name": "Test", "type": "Project"}}} + { + "status": 200, + "body": { + "id": 789, + "name": "todoset", + "title": "To-dos", + "status": "active", + "enabled": true, + "position": 1, + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z", + "url": "https://3.basecampapi.com/999/buckets/456/todosets/789.json", + "app_url": "https://3.basecamp.com/999/buckets/456/todosets/789", + "bucket": { + "id": 456, + "name": "Test", + "type": "Project" + } + } + } ], "assertions": [ - {"type": "requestPath", "expected": "/999/dock/tools/789"}, - {"type": "noError"} + { + "type": "requestPath", + "expected": "/999/dock/tools/789" + }, + { + "type": "noError" + } ], - "tags": ["path", "tools"] + "tags": [ + "path", + "tools" + ] }, { "name": "CreateTool uses bucket-scoped /buckets/{bucketId}/dock/tools.json path", @@ -150,16 +363,48 @@ "operation": "CreateTool", "method": "POST", "path": "/buckets/{bucketId}/dock/tools.json", - "pathParams": {"bucketId": 456}, - "requestBody": {"tool_type": "Message::Board", "title": "Message Board (Copy)"}, + "pathParams": { + "bucketId": 456 + }, + "requestBody": { + "tool_type": "Message::Board", + "title": "Message Board (Copy)" + }, "mockResponses": [ - {"status": 201, "body": {"id": 800, "name": "message_board", "title": "Message Board (Copy)", "status": "active", "enabled": true, "position": 5, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", "url": "https://3.basecampapi.com/999/buckets/456/message_boards/800.json", "app_url": "https://3.basecamp.com/999/buckets/456/message_boards/800", "bucket": {"id": 456, "name": "Test", "type": "Project"}}} + { + "status": 201, + "body": { + "id": 800, + "name": "message_board", + "title": "Message Board (Copy)", + "status": "active", + "enabled": true, + "position": 5, + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z", + "url": "https://3.basecampapi.com/999/buckets/456/message_boards/800.json", + "app_url": "https://3.basecamp.com/999/buckets/456/message_boards/800", + "bucket": { + "id": 456, + "name": "Test", + "type": "Project" + } + } + } ], "assertions": [ - {"type": "requestPath", "expected": "/999/buckets/456/dock/tools.json"}, - {"type": "noError"} + { + "type": "requestPath", + "expected": "/999/buckets/456/dock/tools.json" + }, + { + "type": "noError" + } ], - "tags": ["path", "tools"] + "tags": [ + "path", + "tools" + ] }, { "name": "CreateTool accepts omitted title on bucket-scoped path", @@ -167,16 +412,47 @@ "operation": "CreateTool", "method": "POST", "path": "/buckets/{bucketId}/dock/tools.json", - "pathParams": {"bucketId": 456}, - "requestBody": {"tool_type": "Message::Board"}, + "pathParams": { + "bucketId": 456 + }, + "requestBody": { + "tool_type": "Message::Board" + }, "mockResponses": [ - {"status": 201, "body": {"id": 801, "name": "message_board", "title": "Message Board", "status": "active", "enabled": true, "position": 6, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", "url": "https://3.basecampapi.com/999/buckets/456/message_boards/801.json", "app_url": "https://3.basecamp.com/999/buckets/456/message_boards/801", "bucket": {"id": 456, "name": "Test", "type": "Project"}}} + { + "status": 201, + "body": { + "id": 801, + "name": "message_board", + "title": "Message Board", + "status": "active", + "enabled": true, + "position": 6, + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z", + "url": "https://3.basecampapi.com/999/buckets/456/message_boards/801.json", + "app_url": "https://3.basecamp.com/999/buckets/456/message_boards/801", + "bucket": { + "id": 456, + "name": "Test", + "type": "Project" + } + } + } ], "assertions": [ - {"type": "requestPath", "expected": "/999/buckets/456/dock/tools.json"}, - {"type": "noError"} + { + "type": "requestPath", + "expected": "/999/buckets/456/dock/tools.json" + }, + { + "type": "noError" + } ], - "tags": ["path", "tools"] + "tags": [ + "path", + "tools" + ] }, { "name": "EnableTool uses flat /recordings/{toolId}/position.json path (no bucketId)", @@ -184,15 +460,28 @@ "operation": "EnableTool", "method": "POST", "path": "/recordings/{toolId}/position.json", - "pathParams": {"toolId": 789}, + "pathParams": { + "toolId": 789 + }, "mockResponses": [ - {"status": 201, "body": {}} + { + "status": 201, + "body": {} + } ], "assertions": [ - {"type": "requestPath", "expected": "/999/recordings/789/position.json"}, - {"type": "noError"} + { + "type": "requestPath", + "expected": "/999/recordings/789/position.json" + }, + { + "type": "noError" + } ], - "tags": ["path", "tools"] + "tags": [ + "path", + "tools" + ] }, { "name": "Mixed-case host and explicit default port stay on the mocked origin", @@ -200,32 +489,305 @@ "operation": "GetTool", "method": "GET", "path": "/dock/tools/{toolId}", - "pathParams": {"toolId": 789}, - "configOverrides": {"baseUrl": "https://3.BasecampAPI.com:443"}, + "pathParams": { + "toolId": 789 + }, + "configOverrides": { + "baseUrl": "https://3.BasecampAPI.com:443" + }, "mockResponses": [ - {"status": 200, "body": {"id": 789, "name": "todoset", "title": "To-dos", "status": "active", "enabled": true, "position": 1, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", "url": "https://3.basecampapi.com/999/buckets/456/todosets/789.json", "app_url": "https://3.basecamp.com/999/buckets/456/todosets/789", "bucket": {"id": 456, "name": "Test", "type": "Project"}}} + { + "status": 200, + "body": { + "id": 789, + "name": "todoset", + "title": "To-dos", + "status": "active", + "enabled": true, + "position": 1, + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z", + "url": "https://3.basecampapi.com/999/buckets/456/todosets/789.json", + "app_url": "https://3.basecamp.com/999/buckets/456/todosets/789", + "bucket": { + "id": 456, + "name": "Test", + "type": "Project" + } + } + } ], "assertions": [ - {"type": "requestCount", "expected": 1}, - {"type": "noError"} + { + "type": "requestCount", + "expected": 1 + }, + { + "type": "noError" + } ], - "tags": ["path", "origin", "normalization"] + "tags": [ + "path", + "origin", + "normalization" + ] }, { "name": "Bracketed IPv6 loopback origin stays on the mocked origin", - "description": "Regression for runner origin interception with IPv6: a configOverrides.baseUrl of http://[::1]:3000 (the SPEC §16 IPv6 example; loopback is exempt from HTTPS enforcement) must keep its brackets through origin normalization so the request is served by the mock queue. Runners that dial baseUrl directly rather than intercepting by origin may skip.", + "description": "Regression for runner origin interception with IPv6: a configOverrides.baseUrl of http://[::1]:3000 (the SPEC \u00a716 IPv6 example; loopback is exempt from HTTPS enforcement) must keep its brackets through origin normalization so the request is served by the mock queue. Runners that dial baseUrl directly rather than intercepting by origin may skip.", "operation": "GetTool", "method": "GET", "path": "/dock/tools/{toolId}", - "pathParams": {"toolId": 789}, - "configOverrides": {"baseUrl": "http://[::1]:3000"}, + "pathParams": { + "toolId": 789 + }, + "configOverrides": { + "baseUrl": "http://[::1]:3000" + }, "mockResponses": [ - {"status": 200, "body": {"id": 789, "name": "todoset", "title": "To-dos", "status": "active", "enabled": true, "position": 1, "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z", "url": "https://3.basecampapi.com/999/buckets/456/todosets/789.json", "app_url": "https://3.basecamp.com/999/buckets/456/todosets/789", "bucket": {"id": 456, "name": "Test", "type": "Project"}}} + { + "status": 200, + "body": { + "id": 789, + "name": "todoset", + "title": "To-dos", + "status": "active", + "enabled": true, + "position": 1, + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z", + "url": "https://3.basecampapi.com/999/buckets/456/todosets/789.json", + "app_url": "https://3.basecamp.com/999/buckets/456/todosets/789", + "bucket": { + "id": 456, + "name": "Test", + "type": "Project" + } + } + } ], "assertions": [ - {"type": "requestCount", "expected": 1}, - {"type": "noError"} + { + "type": "requestCount", + "expected": 1 + }, + { + "type": "noError" + } ], - "tags": ["path", "origin", "normalization", "ipv6"] + "tags": [ + "path", + "origin", + "normalization", + "ipv6" + ] + }, + { + "name": "GetEverythingMessages constructs /messages.json", + "description": "Verifies GetEverythingMessages constructs the flat top-level URL path /messages.json (the /everything/ segment is the Rails controller namespace, not part of the URL).", + "operation": "GetEverythingMessages", + "method": "GET", + "path": "/messages.json", + "mockResponses": [ + { + "status": 200, + "body": [] + } + ], + "assertions": [ + { + "type": "requestPath", + "expected": "/999/messages.json" + }, + { + "type": "noError" + } + ], + "tags": [ + "path", + "everything" + ] + }, + { + "name": "GetEverythingComments constructs /comments.json", + "description": "Verifies GetEverythingComments constructs the flat top-level URL path /comments.json (the /everything/ segment is the Rails controller namespace, not part of the URL).", + "operation": "GetEverythingComments", + "method": "GET", + "path": "/comments.json", + "mockResponses": [ + { + "status": 200, + "body": [] + } + ], + "assertions": [ + { + "type": "requestPath", + "expected": "/999/comments.json" + }, + { + "type": "noError" + } + ], + "tags": [ + "path", + "everything" + ] + }, + { + "name": "GetEverythingCheckins constructs /checkins.json", + "description": "Verifies GetEverythingCheckins constructs the flat top-level URL path /checkins.json (the /everything/ segment is the Rails controller namespace, not part of the URL).", + "operation": "GetEverythingCheckins", + "method": "GET", + "path": "/checkins.json", + "mockResponses": [ + { + "status": 200, + "body": [] + } + ], + "assertions": [ + { + "type": "requestPath", + "expected": "/999/checkins.json" + }, + { + "type": "noError" + } + ], + "tags": [ + "path", + "everything" + ] + }, + { + "name": "GetEverythingForwards constructs /forwards.json", + "description": "Verifies GetEverythingForwards constructs the flat top-level URL path /forwards.json (the /everything/ segment is the Rails controller namespace, not part of the URL).", + "operation": "GetEverythingForwards", + "method": "GET", + "path": "/forwards.json", + "mockResponses": [ + { + "status": 200, + "body": [] + } + ], + "assertions": [ + { + "type": "requestPath", + "expected": "/999/forwards.json" + }, + { + "type": "noError" + } + ], + "tags": [ + "path", + "everything" + ] + }, + { + "name": "GetEverythingBoosts constructs /boosts.json", + "description": "Verifies GetEverythingBoosts constructs the flat top-level URL path /boosts.json (the /everything/ segment is the Rails controller namespace, not part of the URL).", + "operation": "GetEverythingBoosts", + "method": "GET", + "path": "/boosts.json", + "mockResponses": [ + { + "status": 200, + "body": [] + } + ], + "assertions": [ + { + "type": "requestPath", + "expected": "/999/boosts.json" + }, + { + "type": "noError" + } + ], + "tags": [ + "path", + "everything" + ] + }, + { + "name": "GetEverythingFiles constructs /files.json", + "description": "Verifies GetEverythingFiles constructs the flat top-level URL path /files.json (the /everything/ segment is the Rails controller namespace, not part of the URL).", + "operation": "GetEverythingFiles", + "method": "GET", + "path": "/files.json", + "mockResponses": [ + { + "status": 200, + "body": [] + } + ], + "assertions": [ + { + "type": "requestPath", + "expected": "/999/files.json" + }, + { + "type": "noError" + } + ], + "tags": [ + "path", + "everything" + ] + }, + { + "name": "GetEverythingOverdueTodos constructs /todos/overdue.json", + "description": "Verifies GetEverythingOverdueTodos constructs the flat top-level URL path /todos/overdue.json (the /everything/ segment is the Rails controller namespace, not part of the URL).", + "operation": "GetEverythingOverdueTodos", + "method": "GET", + "path": "/todos/overdue.json", + "mockResponses": [ + { + "status": 200, + "body": [] + } + ], + "assertions": [ + { + "type": "requestPath", + "expected": "/999/todos/overdue.json" + }, + { + "type": "noError" + } + ], + "tags": [ + "path", + "everything" + ] + }, + { + "name": "GetEverythingOverdueCards constructs /cards/overdue.json", + "description": "Verifies GetEverythingOverdueCards constructs the flat top-level URL path /cards/overdue.json (the /everything/ segment is the Rails controller namespace, not part of the URL).", + "operation": "GetEverythingOverdueCards", + "method": "GET", + "path": "/cards/overdue.json", + "mockResponses": [ + { + "status": 200, + "body": [] + } + ], + "assertions": [ + { + "type": "requestPath", + "expected": "/999/cards/overdue.json" + }, + { + "type": "noError" + } + ], + "tags": [ + "path", + "everything" + ] } ] diff --git a/go/pkg/basecamp/client.go b/go/pkg/basecamp/client.go index c358c487d..b45409964 100644 --- a/go/pkg/basecamp/client.go +++ b/go/pkg/basecamp/client.go @@ -108,6 +108,7 @@ type AccountClient struct { gauges *GaugesService myAssignments *MyAssignmentsService myNotifications *MyNotificationsService + everything *EverythingService } // Response wraps an API response. @@ -1336,6 +1337,16 @@ func (ac *AccountClient) Timeline() *TimelineService { return ac.timeline } +// Everything returns the EverythingService for account-wide aggregate listings. +func (ac *AccountClient) Everything() *EverythingService { + ac.mu.Lock() + defer ac.mu.Unlock() + if ac.everything == nil { + ac.everything = NewEverythingService(ac) + } + return ac.everything +} + // Reports returns the ReportsService for reports operations. func (ac *AccountClient) Reports() *ReportsService { ac.mu.Lock() diff --git a/go/pkg/basecamp/everything.go b/go/pkg/basecamp/everything.go new file mode 100644 index 000000000..89068644e --- /dev/null +++ b/go/pkg/basecamp/everything.go @@ -0,0 +1,465 @@ +package basecamp + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/basecamp/basecamp-sdk/go/pkg/generated" +) + +// EverythingService exposes the account-wide "everything" aggregate listings: +// recency-ordered, paginated roots (messages, comments, checkins, forwards, +// files, boosts) and the unpaginated oldest-first overdue todo/card lists. Each +// item embeds its bucket for project context. See +// spec/api-gaps/everything-aggregates.md. +type EverythingService struct { + client *AccountClient +} + +// NewEverythingService creates a new EverythingService. +func NewEverythingService(client *AccountClient) *EverythingService { + return &EverythingService{client: client} +} + +// RecordingsPage is a page-followed list of recordings with pagination metadata. +type RecordingsPage struct { + Recordings []Recording + Meta ListMeta +} + +// EverythingBoostsPage is a page-followed list of boosts with pagination metadata. +type EverythingBoostsPage struct { + Boosts []Boost + Meta ListMeta +} + +// EverythingFilesPage is a page-followed list of files with pagination metadata. +type EverythingFilesPage struct { + Files []EverythingFile + Meta ListMeta +} + +// EverythingFilesOptions specifies optional filters for the files feed. +type EverythingFilesOptions struct { + // Kind filters by file kind: "all" (default), "images", "pdfs", + // "documents", or "videos". + Kind string + // PeopleIDs restricts the list to files created by the given people. + PeopleIDs []int64 +} + +// EverythingFile is a single item in the /files.json feed: an optional-field +// superset over three wire variants — a full Upload recording, a Basecamp +// Document recording, and a rich-text attachment wrapped in a recording envelope +// (distinguished by AttachableSGID and blob metadata). Only the fields of the +// variant an instance represents are populated. +type EverythingFile struct { + ID int64 `json:"id,omitempty"` + Status string `json:"status,omitempty"` + // VisibleToClients/CreatedAt/UpdatedAt/InheritsStatus are pointers so an + // absent field (the variant this instance is not) stays nil and round-trips + // as omitted rather than a fabricated zero timestamp or a dropped false. + VisibleToClients *bool `json:"visible_to_clients,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` + Title string `json:"title,omitempty"` + InheritsStatus *bool `json:"inherits_status,omitempty"` + // Type is "Upload", "Document", or "Attachment". + Type string `json:"type,omitempty"` + URL string `json:"url,omitempty"` + AppURL string `json:"app_url,omitempty"` + BookmarkURL string `json:"bookmark_url,omitempty"` + SubscriptionURL string `json:"subscription_url,omitempty"` + CommentsCount int32 `json:"comments_count,omitempty"` + CommentsURL string `json:"comments_url,omitempty"` + BoostsCount int32 `json:"boosts_count,omitempty"` + BoostsURL string `json:"boosts_url,omitempty"` + Position int32 `json:"position,omitempty"` + Parent *Parent `json:"parent,omitempty"` + Bucket *Bucket `json:"bucket,omitempty"` + Creator *Person `json:"creator,omitempty"` + // AttachableSGID is present on the rich-text attachment variant only. + AttachableSGID string `json:"attachable_sgid,omitempty"` + // Blob/file metadata (uploads and attachments). + ContentType string `json:"content_type,omitempty"` + ByteSize int64 `json:"byte_size,omitempty"` + Filename string `json:"filename,omitempty"` + DownloadURL string `json:"download_url,omitempty"` + AppDownloadURL string `json:"app_download_url,omitempty"` + // Width and Height are null for non-image blobs and may be float-spelled + // (1024.0) on the wire; narrowed to *int32 here (nil when absent/null). + Width *int32 `json:"width,omitempty"` + Height *int32 `json:"height,omitempty"` + Description string `json:"description,omitempty"` + // DescriptionAttachments carries the rich-text companion array for the + // upload/document Description (absent on the attachment variant). + DescriptionAttachments *[]RichTextAttachment `json:"description_attachments,omitempty"` +} + +// UnmarshalJSON routes decoding through the generated EverythingFile so the +// public struct handles the float-encoded integers (1024.0) and null dimensions +// the BC3 API emits for width/height. Mirrors TimelineAttachment.UnmarshalJSON. +func (f *EverythingFile) UnmarshalJSON(data []byte) error { + var gf generated.EverythingFile + if err := json.Unmarshal(data, &gf); err != nil { + return err + } + *f = everythingFileFromGenerated(gf) + return nil +} + +// everythingFileFromGenerated converts a generated EverythingFile (the +// optional-field superset) to the clean public type. Width and Height are +// optional/nullable *types.FlexInt in the generated type; a nil pointer leaves +// the public *int32 nil, and a present value is narrowed to int32. +func everythingFileFromGenerated(gf generated.EverythingFile) EverythingFile { + f := EverythingFile{ + Status: gf.Status, + VisibleToClients: gf.VisibleToClients, + CreatedAt: gf.CreatedAt, + UpdatedAt: gf.UpdatedAt, + Title: gf.Title, + InheritsStatus: gf.InheritsStatus, + Type: gf.Type, + URL: gf.Url, + AppURL: gf.AppUrl, + BookmarkURL: gf.BookmarkUrl, + SubscriptionURL: gf.SubscriptionUrl, + CommentsCount: gf.CommentsCount, + CommentsURL: gf.CommentsUrl, + BoostsCount: gf.BoostsCount, + BoostsURL: gf.BoostsUrl, + Position: gf.Position, + AttachableSGID: gf.AttachableSgid, + ContentType: gf.ContentType, + ByteSize: gf.ByteSize, + Filename: gf.Filename, + DownloadURL: gf.DownloadUrl, + AppDownloadURL: gf.AppDownloadUrl, + Description: gf.Description, + } + if gf.Id != nil { + f.ID = *gf.Id + } + if gf.Width != nil { + w := int32(*gf.Width) + f.Width = &w + } + if gf.Height != nil { + h := int32(*gf.Height) + f.Height = &h + } + if gf.Parent.Id != 0 || gf.Parent.Title != "" { + f.Parent = &Parent{ID: gf.Parent.Id, Title: gf.Parent.Title, Type: gf.Parent.Type, URL: gf.Parent.Url, AppURL: gf.Parent.AppUrl} + } + if gf.Bucket.Id != 0 || gf.Bucket.Name != "" { + f.Bucket = &Bucket{ID: gf.Bucket.Id, Name: gf.Bucket.Name, Type: gf.Bucket.Type} + } + if gf.Creator.Id != 0 || gf.Creator.Name != "" { + creator := personFromGenerated(gf.Creator) + f.Creator = &creator + } + f.DescriptionAttachments = richTextAttachmentsPtrFromGenerated(gf.DescriptionAttachments) + return f +} + +// Messages returns every message across all accessible projects, newest-first. +// Pass a positive page to return only that page; page 0 follows the Link header +// across all pages. +func (s *EverythingService) Messages(ctx context.Context, page int32) (result *RecordingsPage, err error) { + op := OperationInfo{Service: "Everything", Operation: "Messages", ResourceType: "recording", IsMutation: false} + ctx, done, err := s.begin(ctx, op) + if err != nil { + return nil, err + } + defer done(&err) + var params *generated.GetEverythingMessagesParams + if page > 0 { + params = &generated.GetEverythingMessagesParams{Page: page} + } + resp, err := s.client.parent.gen.GetEverythingMessagesWithResponse(ctx, s.client.accountID, params) + if err != nil { + return nil, err + } + return s.finishRecordingsPage(ctx, resp.HTTPResponse, resp.Body, resp.JSON200, page) +} + +// Comments returns every comment across all accessible projects, newest-first. +func (s *EverythingService) Comments(ctx context.Context, page int32) (result *RecordingsPage, err error) { + op := OperationInfo{Service: "Everything", Operation: "Comments", ResourceType: "recording", IsMutation: false} + ctx, done, err := s.begin(ctx, op) + if err != nil { + return nil, err + } + defer done(&err) + var params *generated.GetEverythingCommentsParams + if page > 0 { + params = &generated.GetEverythingCommentsParams{Page: page} + } + resp, err := s.client.parent.gen.GetEverythingCommentsWithResponse(ctx, s.client.accountID, params) + if err != nil { + return nil, err + } + return s.finishRecordingsPage(ctx, resp.HTTPResponse, resp.Body, resp.JSON200, page) +} + +// Checkins returns every automatic check-in answer across all accessible +// projects, newest-first. +func (s *EverythingService) Checkins(ctx context.Context, page int32) (result *RecordingsPage, err error) { + op := OperationInfo{Service: "Everything", Operation: "Checkins", ResourceType: "recording", IsMutation: false} + ctx, done, err := s.begin(ctx, op) + if err != nil { + return nil, err + } + defer done(&err) + var params *generated.GetEverythingCheckinsParams + if page > 0 { + params = &generated.GetEverythingCheckinsParams{Page: page} + } + resp, err := s.client.parent.gen.GetEverythingCheckinsWithResponse(ctx, s.client.accountID, params) + if err != nil { + return nil, err + } + return s.finishRecordingsPage(ctx, resp.HTTPResponse, resp.Body, resp.JSON200, page) +} + +// Forwards returns every inbox forward across all accessible projects, +// newest-first. +func (s *EverythingService) Forwards(ctx context.Context, page int32) (result *RecordingsPage, err error) { + op := OperationInfo{Service: "Everything", Operation: "Forwards", ResourceType: "recording", IsMutation: false} + ctx, done, err := s.begin(ctx, op) + if err != nil { + return nil, err + } + defer done(&err) + var params *generated.GetEverythingForwardsParams + if page > 0 { + params = &generated.GetEverythingForwardsParams{Page: page} + } + resp, err := s.client.parent.gen.GetEverythingForwardsWithResponse(ctx, s.client.accountID, params) + if err != nil { + return nil, err + } + return s.finishRecordingsPage(ctx, resp.HTTPResponse, resp.Body, resp.JSON200, page) +} + +// finishRecordingsPage decodes the first page of a []Recording aggregate root +// and follows the Link header (unless a positive page was requested). +func (s *EverythingService) finishRecordingsPage(ctx context.Context, httpResp *http.Response, body []byte, json200 *[]generated.Recording, page int32) (*RecordingsPage, error) { + if err := checkResponse(httpResp, body); err != nil { + return nil, err + } + var recordings []Recording + if json200 != nil { + for _, gr := range *json200 { + recordings = append(recordings, recordingFromGenerated(gr)) + } + } + totalCount := parseTotalCount(httpResp) + if page > 0 { + return &RecordingsPage{Recordings: recordings, Meta: ListMeta{TotalCount: totalCount}}, nil + } + rawMore, truncated, err := s.client.parent.followPagination(ctx, httpResp, len(recordings), 0) + if err != nil { + return nil, err + } + for _, raw := range rawMore { + var gr generated.Recording + if err := json.Unmarshal(raw, &gr); err != nil { + return nil, fmt.Errorf("failed to parse recording: %w", err) + } + recordings = append(recordings, recordingFromGenerated(gr)) + } + return &RecordingsPage{Recordings: recordings, Meta: ListMeta{TotalCount: totalCount, Truncated: truncated}}, nil +} + +// Boosts returns every boost across all accessible projects, newest-first. Each +// boost carries its booster and the recording it boosts. +func (s *EverythingService) Boosts(ctx context.Context, page int32) (result *EverythingBoostsPage, err error) { + op := OperationInfo{Service: "Everything", Operation: "Boosts", ResourceType: "boost", IsMutation: false} + ctx, done, err := s.begin(ctx, op) + if err != nil { + return nil, err + } + defer done(&err) + + var params *generated.GetEverythingBoostsParams + if page > 0 { + params = &generated.GetEverythingBoostsParams{Page: page} + } + resp, err := s.client.parent.gen.GetEverythingBoostsWithResponse(ctx, s.client.accountID, params) + if err != nil { + return nil, err + } + if err = checkResponse(resp.HTTPResponse, resp.Body); err != nil { + return nil, err + } + + var boosts []Boost + if resp.JSON200 != nil { + for _, gb := range *resp.JSON200 { + boosts = append(boosts, boostFromGenerated(gb)) + } + } + totalCount := parseTotalCount(resp.HTTPResponse) + if page > 0 { + return &EverythingBoostsPage{Boosts: boosts, Meta: ListMeta{TotalCount: totalCount}}, nil + } + rawMore, truncated, err := s.client.parent.followPagination(ctx, resp.HTTPResponse, len(boosts), 0) + if err != nil { + return nil, err + } + for _, raw := range rawMore { + var gb generated.Boost + if err := json.Unmarshal(raw, &gb); err != nil { + return nil, fmt.Errorf("failed to parse boost: %w", err) + } + boosts = append(boosts, boostFromGenerated(gb)) + } + return &EverythingBoostsPage{Boosts: boosts, Meta: ListMeta{TotalCount: totalCount, Truncated: truncated}}, nil +} + +// Files returns every file recording across all accessible projects, +// newest-first. The feed is heterogeneous (uploads, documents, attachments); +// each element is an optional-field superset. Optional filters narrow by kind +// and creator. +func (s *EverythingService) Files(ctx context.Context, page int32, opts *EverythingFilesOptions) (result *EverythingFilesPage, err error) { + op := OperationInfo{Service: "Everything", Operation: "Files", ResourceType: "file", IsMutation: false} + ctx, done, err := s.begin(ctx, op) + if err != nil { + return nil, err + } + defer done(&err) + + var params *generated.GetEverythingFilesParams + if opts != nil || page > 0 { + params = &generated.GetEverythingFilesParams{} + if page > 0 { + params.Page = page + } + if opts != nil { + if opts.Kind != "" { + params.Kind = opts.Kind + } + if len(opts.PeopleIDs) > 0 { + ids := append([]int64(nil), opts.PeopleIDs...) + params.PeopleIds = &ids + } + } + } + + resp, err := s.client.parent.gen.GetEverythingFilesWithResponse(ctx, s.client.accountID, params) + if err != nil { + return nil, err + } + if err = checkResponse(resp.HTTPResponse, resp.Body); err != nil { + return nil, err + } + + files, err := decodeEverythingFiles(resp.Body) + if err != nil { + return nil, err + } + totalCount := parseTotalCount(resp.HTTPResponse) + if page > 0 { + return &EverythingFilesPage{Files: files, Meta: ListMeta{TotalCount: totalCount}}, nil + } + rawMore, truncated, err := s.client.parent.followPagination(ctx, resp.HTTPResponse, len(files), 0) + if err != nil { + return nil, err + } + for _, raw := range rawMore { + var gf generated.EverythingFile + if err := json.Unmarshal(raw, &gf); err != nil { + return nil, fmt.Errorf("failed to parse file: %w", err) + } + files = append(files, everythingFileFromGenerated(gf)) + } + return &EverythingFilesPage{Files: files, Meta: ListMeta{TotalCount: totalCount, Truncated: truncated}}, nil +} + +// decodeEverythingFiles decodes a files page (a bare JSON array) into clean +// EverythingFile values via the superset's UnmarshalJSON (which routes through +// the generated FlexInt-aware type). +func decodeEverythingFiles(body []byte) ([]EverythingFile, error) { + var files []EverythingFile + if err := json.Unmarshal(body, &files); err != nil { + return nil, fmt.Errorf("failed to parse files: %w", err) + } + return files, nil +} + +// OverdueTodos returns every overdue to-do across all accessible projects — a +// complete, oldest-due-date-first array (unpaginated, no Link-following). +func (s *EverythingService) OverdueTodos(ctx context.Context) (result []Todo, err error) { + op := OperationInfo{Service: "Everything", Operation: "OverdueTodos", ResourceType: "todo", IsMutation: false} + ctx, done, err := s.begin(ctx, op) + if err != nil { + return nil, err + } + defer done(&err) + + resp, err := s.client.parent.gen.GetEverythingOverdueTodosWithResponse(ctx, s.client.accountID) + if err != nil { + return nil, err + } + if err = checkResponse(resp.HTTPResponse, resp.Body); err != nil { + return nil, err + } + var todos []Todo + if resp.JSON200 != nil { + for _, gt := range *resp.JSON200 { + todos = append(todos, todoFromGenerated(gt)) + } + } + return todos, nil +} + +// OverdueCards returns every overdue card across all accessible projects — a +// complete, oldest-due-date-first array (unpaginated, no Link-following). +func (s *EverythingService) OverdueCards(ctx context.Context) (result []Card, err error) { + op := OperationInfo{Service: "Everything", Operation: "OverdueCards", ResourceType: "card", IsMutation: false} + ctx, done, err := s.begin(ctx, op) + if err != nil { + return nil, err + } + defer done(&err) + + resp, err := s.client.parent.gen.GetEverythingOverdueCardsWithResponse(ctx, s.client.accountID) + if err != nil { + return nil, err + } + if err = checkResponse(resp.HTTPResponse, resp.Body); err != nil { + return nil, err + } + var cards []Card + if resp.JSON200 != nil { + for _, gc := range *resp.JSON200 { + cards = append(cards, cardFromGenerated(gc)) + } + } + return cards, nil +} + +// begin runs the gating + start/end hook lifecycle shared by the everything +// methods and returns the (possibly gated) context plus a deferred finisher. +func (s *EverythingService) begin(ctx context.Context, op OperationInfo) (context.Context, func(*error), error) { + if gater, ok := s.client.parent.hooks.(GatingHooks); ok { + var err error + if ctx, err = gater.OnOperationGate(ctx, op); err != nil { + return ctx, func(*error) {}, err + } + } + start := time.Now() + ctx = s.client.parent.hooks.OnOperationStart(ctx, op) + return ctx, func(errp *error) { + var e error + if errp != nil { + e = *errp + } + s.client.parent.hooks.OnOperationEnd(ctx, op, e, time.Since(start)) + }, nil +} diff --git a/go/pkg/basecamp/everything_test.go b/go/pkg/basecamp/everything_test.go new file mode 100644 index 000000000..965c25d6c --- /dev/null +++ b/go/pkg/basecamp/everything_test.go @@ -0,0 +1,206 @@ +package basecamp + +import ( + "context" + "fmt" + "net/http" + "net/http/httptest" + "net/url" + "testing" +) + +func everythingTestClient(t *testing.T, handler http.HandlerFunc) (*EverythingService, string) { + t.Helper() + server := httptest.NewServer(handler) + t.Cleanup(server.Close) + cfg := DefaultConfig() + cfg.BaseURL = server.URL + client := NewClient(cfg, &StaticTokenProvider{Token: "test-token"}) + return client.ForAccount("99999").Everything(), server.URL +} + +// TestEverythingService_Messages_MultiPage exercises Link-header following and +// X-Total-Count metadata across two pages of the /messages.json root. +func TestEverythingService_Messages_MultiPage(t *testing.T) { + var serverURL string + var page1, page2 int + svc, url := everythingTestClient(t, func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/99999/messages.json" { + t.Errorf("unexpected path: %s", r.URL.Path) + } + w.Header().Set("Content-Type", "application/json") + w.Header().Set("X-Total-Count", "3") + if r.URL.Query().Get("page") == "2" { + page2++ + w.WriteHeader(200) + _, _ = w.Write([]byte(`[{"id":3,"type":"Message","title":"Third","url":"https://x/3.json","status":"active","visible_to_clients":false,"created_at":"2026-01-01T00:00:00Z","updated_at":"2026-01-01T00:00:00Z","inherits_status":true,"app_url":"https://x/3","bucket":{"id":9,"name":"P","type":"Project"},"parent":{"id":8,"title":"MB","type":"Message::Board"},"creator":{"id":1,"name":"A"}}]`)) + return + } + page1++ + w.Header().Set("Link", fmt.Sprintf(`<%s/99999/messages.json?page=2>; rel="next"`, serverURL)) + w.WriteHeader(200) + _, _ = w.Write([]byte(`[{"id":1,"type":"Message","title":"First","url":"https://x/1.json","status":"active","visible_to_clients":false,"created_at":"2026-01-01T00:00:00Z","updated_at":"2026-01-01T00:00:00Z","inherits_status":true,"app_url":"https://x/1","bucket":{"id":9,"name":"P","type":"Project"},"parent":{"id":8,"title":"MB","type":"Message::Board"},"creator":{"id":1,"name":"A"}},{"id":2,"type":"Message","title":"Second","url":"https://x/2.json","status":"active","visible_to_clients":false,"created_at":"2026-01-01T00:00:00Z","updated_at":"2026-01-01T00:00:00Z","inherits_status":true,"app_url":"https://x/2","bucket":{"id":9,"name":"P","type":"Project"},"parent":{"id":8,"title":"MB","type":"Message::Board"},"creator":{"id":1,"name":"A"}}]`)) + }) + serverURL = url + + result, err := svc.Messages(context.Background(), 0) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if page1 != 1 || page2 != 1 { + t.Fatalf("expected one hit per page, got page1=%d page2=%d", page1, page2) + } + if len(result.Recordings) != 3 { + t.Fatalf("expected 3 recordings across pages, got %d", len(result.Recordings)) + } + if result.Recordings[0].ID != 1 || result.Recordings[2].ID != 3 { + t.Errorf("unexpected ordering across pages: %d..%d", result.Recordings[0].ID, result.Recordings[2].ID) + } + if result.Recordings[0].Bucket == nil || result.Recordings[0].Bucket.Name != "P" { + t.Errorf("expected embedded bucket, got %+v", result.Recordings[0].Bucket) + } + if result.Meta.TotalCount != 3 { + t.Errorf("expected TotalCount 3, got %d", result.Meta.TotalCount) + } +} + +// TestEverythingService_OverdueTodos_Unpaginated verifies the overdue list is a +// complete oldest-first array with NO Link-following, even if a Link header is +// present. +func TestEverythingService_OverdueTodos_Unpaginated(t *testing.T) { + var hits int + svc, _ := everythingTestClient(t, func(w http.ResponseWriter, r *http.Request) { + hits++ + if r.URL.Path != "/99999/todos/overdue.json" { + t.Errorf("unexpected path: %s", r.URL.Path) + } + w.Header().Set("Content-Type", "application/json") + // A Link header is present but must NOT be followed for the overdue list. + w.Header().Set("Link", `; rel="next"`) + w.WriteHeader(200) + _, _ = w.Write([]byte(`[ + {"id":10,"content":"Oldest","due_on":"2025-01-01","completed":false}, + {"id":11,"content":"Newer","due_on":"2025-06-01","completed":false} + ]`)) + }) + + todos, err := svc.OverdueTodos(context.Background()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if hits != 1 { + t.Errorf("expected exactly 1 request (no Link-following), got %d", hits) + } + if len(todos) != 2 { + t.Fatalf("expected 2 overdue todos, got %d", len(todos)) + } + if todos[0].ID != 10 || todos[1].ID != 11 { + t.Errorf("expected oldest-first ordering preserved, got %d, %d", todos[0].ID, todos[1].ID) + } +} + +// TestEverythingService_Files_PerVariantDecode proves the heterogeneous +// /files.json feed decodes all three variants — a full Upload recording, a +// Basecamp Document recording, and a rich-text Attachment envelope — in one +// non-empty array, including a float-spelled and a null width. +func TestEverythingService_Files_PerVariantDecode(t *testing.T) { + svc, _ := everythingTestClient(t, func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + _, _ = w.Write([]byte(`[ + {"id":900,"type":"Upload","title":"logo.png","filename":"logo.png","content_type":"image/png","byte_size":1281,"width":1024.0,"height":768.0,"url":"https://x/uploads/900.json","download_url":"https://x/d/900","app_download_url":"https://storage/900","bucket":{"id":9,"name":"P","type":"Project"}}, + {"id":901,"type":"Document","title":"Spec","url":"https://x/documents/901.json","content_type":"text/html","bucket":{"id":9,"name":"P","type":"Project"}}, + {"id":902,"type":"Attachment","attachable_sgid":"sgid-902","filename":"chart.avif","content_type":"image/avif","byte_size":4096,"width":null,"height":null,"download_url":"https://storage/blobs/902","parent":{"id":800,"title":"A message","type":"Message"}} + ]`)) + }) + + result, err := svc.Files(context.Background(), 1, nil) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if len(result.Files) != 3 { + t.Fatalf("expected 3 files, got %d", len(result.Files)) + } + up := result.Files[0] + if up.Type != "Upload" || up.Filename != "logo.png" || up.AppDownloadURL == "" { + t.Errorf("upload variant not decoded: %+v", up) + } + if up.Width == nil || *up.Width != 1024 { + t.Errorf("expected float-spelled width 1024, got %v", up.Width) + } + if up.AttachableSGID != "" { + t.Errorf("upload variant should not carry attachable_sgid") + } + doc := result.Files[1] + if doc.Type != "Document" || doc.Title != "Spec" { + t.Errorf("document variant not decoded: %+v", doc) + } + att := result.Files[2] + if att.Type != "Attachment" || att.AttachableSGID != "sgid-902" || att.Parent == nil { + t.Errorf("attachment variant not decoded: %+v", att) + } + if att.Width != nil || att.Height != nil { + t.Errorf("expected nil width/height for non-image attachment, got w=%v h=%v", att.Width, att.Height) + } +} + +// TestEverythingService_Files_Filters verifies the kind and people_ids[] query +// parameters reach the wire. +func TestEverythingService_Files_Filters(t *testing.T) { + var captured string + svc, _ := everythingTestClient(t, func(w http.ResponseWriter, r *http.Request) { + captured = r.URL.RawQuery + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + _, _ = w.Write([]byte(`[]`)) + }) + + _, err := svc.Files(context.Background(), 1, &EverythingFilesOptions{Kind: "images", PeopleIDs: []int64{101, 202}}) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + q, _ := url.ParseQuery(captured) + if q.Get("kind") != "images" { + t.Errorf("expected kind=images, got query %q", captured) + } + ids := q["people_ids[]"] + if len(ids) != 2 || ids[0] != "101" || ids[1] != "202" { + t.Errorf("expected people_ids[]=101,202, got %v (query %q)", ids, captured) + } +} + +// TestEverythingService_Messages_ForwardsPage verifies that a positive page is +// forwarded as the ?page= query param (regression: previously page>1 silently +// fetched page 1). Also covers the Files kind+page combination. +func TestEverythingService_Messages_ForwardsPage(t *testing.T) { + var captured string + svc, _ := everythingTestClient(t, func(w http.ResponseWriter, r *http.Request) { + captured = r.URL.RawQuery + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + _, _ = w.Write([]byte(`[]`)) + }) + if _, err := svc.Messages(context.Background(), 2); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if q, _ := url.ParseQuery(captured); q.Get("page") != "2" { + t.Errorf("expected page=2 forwarded, got query %q", captured) + } +} + +func TestEverythingService_Files_ForwardsPageAndKind(t *testing.T) { + var captured string + svc, _ := everythingTestClient(t, func(w http.ResponseWriter, r *http.Request) { + captured = r.URL.RawQuery + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + _, _ = w.Write([]byte(`[]`)) + }) + if _, err := svc.Files(context.Background(), 3, &EverythingFilesOptions{Kind: "pdfs"}); err != nil { + t.Fatalf("unexpected error: %v", err) + } + q, _ := url.ParseQuery(captured) + if q.Get("page") != "3" || q.Get("kind") != "pdfs" { + t.Errorf("expected page=3&kind=pdfs, got %q", captured) + } +} diff --git a/go/pkg/basecamp/url-routes.json b/go/pkg/basecamp/url-routes.json index bcf7db246..a3ffc6ded 100644 --- a/go/pkg/basecamp/url-routes.json +++ b/go/pkg/basecamp/url-routes.json @@ -56,6 +56,19 @@ } } }, + { + "pattern": "/{accountId}/boosts", + "resource": "Everything", + "operations": { + "GET": "GetEverythingBoosts" + }, + "params": { + "accountId": { + "role": "account", + "type": "string" + } + } + }, { "pattern": "/{accountId}/boosts/{boostId}", "resource": "Boosts", @@ -404,6 +417,19 @@ } } }, + { + "pattern": "/{accountId}/cards/overdue", + "resource": "Everything", + "operations": { + "GET": "GetEverythingOverdueCards" + }, + "params": { + "accountId": { + "role": "account", + "type": "string" + } + } + }, { "pattern": "/{accountId}/categories", "resource": "Messages", @@ -567,6 +593,19 @@ } } }, + { + "pattern": "/{accountId}/checkins", + "resource": "Everything", + "operations": { + "GET": "GetEverythingCheckins" + }, + "params": { + "accountId": { + "role": "account", + "type": "string" + } + } + }, { "pattern": "/{accountId}/circles/people", "resource": "People", @@ -678,6 +717,19 @@ } } }, + { + "pattern": "/{accountId}/comments", + "resource": "Everything", + "operations": { + "GET": "GetEverythingComments" + }, + "params": { + "accountId": { + "role": "account", + "type": "string" + } + } + }, { "pattern": "/{accountId}/comments/{commentId}", "resource": "Messages", @@ -733,6 +785,32 @@ } } }, + { + "pattern": "/{accountId}/files", + "resource": "Everything", + "operations": { + "GET": "GetEverythingFiles" + }, + "params": { + "accountId": { + "role": "account", + "type": "string" + } + } + }, + { + "pattern": "/{accountId}/forwards", + "resource": "Everything", + "operations": { + "GET": "GetEverythingForwards" + }, + "params": { + "accountId": { + "role": "account", + "type": "string" + } + } + }, { "pattern": "/{accountId}/gauge_needles/{needleId}", "resource": "Gauges", @@ -909,6 +987,19 @@ } } }, + { + "pattern": "/{accountId}/messages", + "resource": "Everything", + "operations": { + "GET": "GetEverythingMessages" + }, + "params": { + "accountId": { + "role": "account", + "type": "string" + } + } + }, { "pattern": "/{accountId}/messages/{messageId}", "resource": "Messages", @@ -2030,6 +2121,19 @@ } } }, + { + "pattern": "/{accountId}/todos/overdue", + "resource": "Everything", + "operations": { + "GET": "GetEverythingOverdueTodos" + }, + "params": { + "accountId": { + "role": "account", + "type": "string" + } + } + }, { "pattern": "/{accountId}/todos/{todoId}", "resource": "Todos", diff --git a/go/pkg/generated/client.gen.go b/go/pkg/generated/client.gen.go index 7a4eaaf08..818ceaf21 100644 --- a/go/pkg/generated/client.gen.go +++ b/go/pkg/generated/client.gen.go @@ -849,6 +849,59 @@ type EventDetails struct { RemovedPersonIds []int64 `json:"removed_person_ids,omitempty"` } +// EverythingFile A single item in the /files.json feed. An optional-field superset over three +// wire variants — a full Upload recording, a Basecamp Document recording, and a +// rich-text attachment wrapped in a recording envelope (distinguished by +// `attachable_sgid` and blob metadata). Every field is optional; a given +// instance populates only the fields of the variant it represents. Unknown +// fields are ignored by every SDK decoder, so the superset need not enumerate +// every field of the Upload/Document recordings. +type EverythingFile struct { + AppDownloadUrl string `json:"app_download_url,omitempty"` + AppUrl string `json:"app_url,omitempty"` + + // AttachableSgid Present on the rich-text attachment variant: signed global id of the + // attachment (uploads/documents omit it). + AttachableSgid string `json:"attachable_sgid,omitempty"` + BookmarkUrl string `json:"bookmark_url,omitempty"` + BoostsCount int32 `json:"boosts_count,omitempty"` + BoostsUrl string `json:"boosts_url,omitempty"` + Bucket RecordingBucket `json:"bucket,omitempty"` + ByteSize int64 `json:"byte_size,omitempty"` + CommentsCount int32 `json:"comments_count,omitempty"` + CommentsUrl string `json:"comments_url,omitempty"` + ContentType string `json:"content_type,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + Creator Person `json:"creator,omitempty"` + + // Description Rich-text description (upload/document variants). + Description string `json:"description,omitempty"` + DescriptionAttachments []RichTextAttachment `json:"description_attachments,omitempty"` + DownloadUrl string `json:"download_url,omitempty"` + Filename string `json:"filename,omitempty"` + + // Height Pixel height; null for non-image blobs and may be float-spelled (1024.0). + Height *types.FlexInt `json:"height,omitempty"` + + // Id Recording (Upload/Document) or attachment id. + Id *int64 `json:"id,omitempty"` + InheritsStatus *bool `json:"inherits_status,omitempty"` + Parent RecordingParent `json:"parent,omitempty"` + Position int32 `json:"position,omitempty"` + Status string `json:"status,omitempty"` + SubscriptionUrl string `json:"subscription_url,omitempty"` + Title string `json:"title,omitempty"` + + // Type "Upload", "Document", or "Attachment". + Type string `json:"type,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` + Url string `json:"url,omitempty"` + VisibleToClients *bool `json:"visible_to_clients,omitempty"` + + // Width Pixel width; null for non-image blobs and may be float-spelled (1024.0). + Width *types.FlexInt `json:"width,omitempty"` +} + // FirstWeekDay defines model for FirstWeekDay. type FirstWeekDay string @@ -1041,6 +1094,30 @@ type GetCommentResponseContent = Comment // GetDocumentResponseContent defines model for GetDocumentResponseContent. type GetDocumentResponseContent = Document +// GetEverythingBoostsResponseContent defines model for GetEverythingBoostsResponseContent. +type GetEverythingBoostsResponseContent = []Boost + +// GetEverythingCheckinsResponseContent defines model for GetEverythingCheckinsResponseContent. +type GetEverythingCheckinsResponseContent = []Recording + +// GetEverythingCommentsResponseContent defines model for GetEverythingCommentsResponseContent. +type GetEverythingCommentsResponseContent = []Recording + +// GetEverythingFilesResponseContent defines model for GetEverythingFilesResponseContent. +type GetEverythingFilesResponseContent = []EverythingFile + +// GetEverythingForwardsResponseContent defines model for GetEverythingForwardsResponseContent. +type GetEverythingForwardsResponseContent = []Recording + +// GetEverythingMessagesResponseContent defines model for GetEverythingMessagesResponseContent. +type GetEverythingMessagesResponseContent = []Recording + +// GetEverythingOverdueCardsResponseContent defines model for GetEverythingOverdueCardsResponseContent. +type GetEverythingOverdueCardsResponseContent = []Card + +// GetEverythingOverdueTodosResponseContent defines model for GetEverythingOverdueTodosResponseContent. +type GetEverythingOverdueTodosResponseContent = []Todo + // GetForwardReplyResponseContent defines model for GetForwardReplyResponseContent. type GetForwardReplyResponseContent = ForwardReply @@ -2994,6 +3071,12 @@ type CreateAttachmentParams struct { Name string `form:"name" json:"name"` } +// GetEverythingBoostsParams defines parameters for GetEverythingBoosts. +type GetEverythingBoostsParams struct { + // Page Page number for paginating through results. Defaults to 1. + Page int32 `form:"page,omitempty" json:"page,omitempty"` +} + // ListCampfireLinesParams defines parameters for ListCampfireLines. type ListCampfireLinesParams struct { // Sort created_at|updated_at @@ -3018,6 +3101,12 @@ type CreateCampfireUploadParams struct { Name string `form:"name" json:"name"` } +// GetEverythingCheckinsParams defines parameters for GetEverythingCheckins. +type GetEverythingCheckinsParams struct { + // Page Page number for paginating through results. Defaults to 1. + Page int32 `form:"page,omitempty" json:"page,omitempty"` +} + // ListClientApprovalsParams defines parameters for ListClientApprovals. type ListClientApprovalsParams struct { // Sort created_at|updated_at @@ -3036,6 +3125,30 @@ type ListClientCorrespondencesParams struct { Direction string `form:"direction,omitempty" json:"direction,omitempty"` } +// GetEverythingCommentsParams defines parameters for GetEverythingComments. +type GetEverythingCommentsParams struct { + // Page Page number for paginating through results. Defaults to 1. + Page int32 `form:"page,omitempty" json:"page,omitempty"` +} + +// GetEverythingFilesParams defines parameters for GetEverythingFiles. +type GetEverythingFilesParams struct { + // Kind Filter by file kind: all (default), images, pdfs, documents, or videos. + Kind string `form:"kind,omitempty" json:"kind,omitempty"` + + // PeopleIds Restrict to files created by the given people (repeatable). + PeopleIds *[]int64 `form:"people_ids[],omitempty" json:"people_ids[],omitempty"` + + // Page Page number for paginating through results. Defaults to 1. + Page int32 `form:"page,omitempty" json:"page,omitempty"` +} + +// GetEverythingForwardsParams defines parameters for GetEverythingForwards. +type GetEverythingForwardsParams struct { + // Page Page number for paginating through results. Defaults to 1. + Page int32 `form:"page,omitempty" json:"page,omitempty"` +} + // ListForwardsParams defines parameters for ListForwards. type ListForwardsParams struct { // Sort created_at|updated_at @@ -3054,6 +3167,12 @@ type ListMessagesParams struct { Direction string `form:"direction,omitempty" json:"direction,omitempty"` } +// GetEverythingMessagesParams defines parameters for GetEverythingMessages. +type GetEverythingMessagesParams struct { + // Page Page number for paginating through results. Defaults to 1. + Page int32 `form:"page,omitempty" json:"page,omitempty"` +} + // GetMyDueAssignmentsParams defines parameters for GetMyDueAssignments. type GetMyDueAssignmentsParams struct { // Scope Filter by due date range: overdue, due_today, due_tomorrow, @@ -3741,6 +3860,9 @@ type ClientInterface interface { // CreateAttachmentWithBody request with any body CreateAttachmentWithBody(ctx context.Context, accountId string, params *CreateAttachmentParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetEverythingBoosts request + GetEverythingBoosts(ctx context.Context, accountId string, params *GetEverythingBoostsParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // DeleteBoost request DeleteBoost(ctx context.Context, accountId string, boostId int64, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -3855,6 +3977,9 @@ type ClientInterface interface { MoveCardColumn(ctx context.Context, accountId string, cardTableId int64, body MoveCardColumnJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetEverythingOverdueCards request + GetEverythingOverdueCards(ctx context.Context, accountId string, reqEditors ...RequestEditorFn) (*http.Response, error) + // ListMessageTypes request ListMessageTypes(ctx context.Context, accountId string, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -3924,6 +4049,9 @@ type ClientInterface interface { // CreateCampfireUploadWithBody request with any body CreateCampfireUploadWithBody(ctx context.Context, accountId string, campfireId int64, params *CreateCampfireUploadParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetEverythingCheckins request + GetEverythingCheckins(ctx context.Context, accountId string, params *GetEverythingCheckinsParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // ListPingablePeople request ListPingablePeople(ctx context.Context, accountId string, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -3945,6 +4073,9 @@ type ClientInterface interface { // GetClientReply request GetClientReply(ctx context.Context, accountId string, recordingId int64, replyId int64, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetEverythingComments request + GetEverythingComments(ctx context.Context, accountId string, params *GetEverythingCommentsParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetComment request GetComment(ctx context.Context, accountId string, commentId int64, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -3972,6 +4103,12 @@ type ClientInterface interface { UpdateDocument(ctx context.Context, accountId string, documentId int64, body UpdateDocumentJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetEverythingFiles request + GetEverythingFiles(ctx context.Context, accountId string, params *GetEverythingFilesParams, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetEverythingForwards request + GetEverythingForwards(ctx context.Context, accountId string, params *GetEverythingForwardsParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // DestroyGaugeNeedle request DestroyGaugeNeedle(ctx context.Context, accountId string, needleId int64, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -4030,6 +4167,9 @@ type ClientInterface interface { CreateMessage(ctx context.Context, accountId string, boardId int64, body CreateMessageJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetEverythingMessages request + GetEverythingMessages(ctx context.Context, accountId string, params *GetEverythingMessagesParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetMessage request GetMessage(ctx context.Context, accountId string, messageId int64, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -4399,6 +4539,9 @@ type ClientInterface interface { CreateTodo(ctx context.Context, accountId string, todolistId int64, body CreateTodoJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetEverythingOverdueTodos request + GetEverythingOverdueTodos(ctx context.Context, accountId string, reqEditors ...RequestEditorFn) (*http.Response, error) + // TrashTodo request TrashTodo(ctx context.Context, accountId string, todoId int64, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -4564,6 +4707,16 @@ func (c *Client) CreateAttachmentWithBody(ctx context.Context, accountId string, } +// GetEverythingBoosts is marked as idempotent and will be retried on transient failures. + +func (c *Client) GetEverythingBoosts(ctx context.Context, accountId string, params *GetEverythingBoostsParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + + return c.doWithRetry(ctx, func() (*http.Request, error) { + return NewGetEverythingBoostsRequest(c.Server, accountId, params) + }, true, "GetEverythingBoosts", reqEditors...) + +} + // DeleteBoost is marked as idempotent and will be retried on transient failures. func (c *Client) DeleteBoost(ctx context.Context, accountId string, boostId int64, reqEditors ...RequestEditorFn) (*http.Response, error) { @@ -5078,6 +5231,16 @@ func (c *Client) MoveCardColumn(ctx context.Context, accountId string, cardTable } +// GetEverythingOverdueCards is marked as idempotent and will be retried on transient failures. + +func (c *Client) GetEverythingOverdueCards(ctx context.Context, accountId string, reqEditors ...RequestEditorFn) (*http.Response, error) { + + return c.doWithRetry(ctx, func() (*http.Request, error) { + return NewGetEverythingOverdueCardsRequest(c.Server, accountId) + }, true, "GetEverythingOverdueCards", reqEditors...) + +} + // ListMessageTypes is marked as idempotent and will be retried on transient failures. func (c *Client) ListMessageTypes(ctx context.Context, accountId string, reqEditors ...RequestEditorFn) (*http.Response, error) { @@ -5358,6 +5521,16 @@ func (c *Client) CreateCampfireUploadWithBody(ctx context.Context, accountId str } +// GetEverythingCheckins is marked as idempotent and will be retried on transient failures. + +func (c *Client) GetEverythingCheckins(ctx context.Context, accountId string, params *GetEverythingCheckinsParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + + return c.doWithRetry(ctx, func() (*http.Request, error) { + return NewGetEverythingCheckinsRequest(c.Server, accountId, params) + }, true, "GetEverythingCheckins", reqEditors...) + +} + // ListPingablePeople is marked as idempotent and will be retried on transient failures. func (c *Client) ListPingablePeople(ctx context.Context, accountId string, reqEditors ...RequestEditorFn) (*http.Response, error) { @@ -5428,6 +5601,16 @@ func (c *Client) GetClientReply(ctx context.Context, accountId string, recording } +// GetEverythingComments is marked as idempotent and will be retried on transient failures. + +func (c *Client) GetEverythingComments(ctx context.Context, accountId string, params *GetEverythingCommentsParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + + return c.doWithRetry(ctx, func() (*http.Request, error) { + return NewGetEverythingCommentsRequest(c.Server, accountId, params) + }, true, "GetEverythingComments", reqEditors...) + +} + // GetComment is marked as idempotent and will be retried on transient failures. func (c *Client) GetComment(ctx context.Context, accountId string, commentId int64, reqEditors ...RequestEditorFn) (*http.Response, error) { @@ -5522,6 +5705,26 @@ func (c *Client) UpdateDocument(ctx context.Context, accountId string, documentI } +// GetEverythingFiles is marked as idempotent and will be retried on transient failures. + +func (c *Client) GetEverythingFiles(ctx context.Context, accountId string, params *GetEverythingFilesParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + + return c.doWithRetry(ctx, func() (*http.Request, error) { + return NewGetEverythingFilesRequest(c.Server, accountId, params) + }, true, "GetEverythingFiles", reqEditors...) + +} + +// GetEverythingForwards is marked as idempotent and will be retried on transient failures. + +func (c *Client) GetEverythingForwards(ctx context.Context, accountId string, params *GetEverythingForwardsParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + + return c.doWithRetry(ctx, func() (*http.Request, error) { + return NewGetEverythingForwardsRequest(c.Server, accountId, params) + }, true, "GetEverythingForwards", reqEditors...) + +} + // DestroyGaugeNeedle is marked as idempotent and will be retried on transient failures. func (c *Client) DestroyGaugeNeedle(ctx context.Context, accountId string, needleId int64, reqEditors ...RequestEditorFn) (*http.Response, error) { @@ -5758,6 +5961,16 @@ func (c *Client) CreateMessage(ctx context.Context, accountId string, boardId in } +// GetEverythingMessages is marked as idempotent and will be retried on transient failures. + +func (c *Client) GetEverythingMessages(ctx context.Context, accountId string, params *GetEverythingMessagesParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + + return c.doWithRetry(ctx, func() (*http.Request, error) { + return NewGetEverythingMessagesRequest(c.Server, accountId, params) + }, true, "GetEverythingMessages", reqEditors...) + +} + // GetMessage is marked as idempotent and will be retried on transient failures. func (c *Client) GetMessage(ctx context.Context, accountId string, messageId int64, reqEditors ...RequestEditorFn) (*http.Response, error) { @@ -7218,6 +7431,16 @@ func (c *Client) CreateTodo(ctx context.Context, accountId string, todolistId in } +// GetEverythingOverdueTodos is marked as idempotent and will be retried on transient failures. + +func (c *Client) GetEverythingOverdueTodos(ctx context.Context, accountId string, reqEditors ...RequestEditorFn) (*http.Response, error) { + + return c.doWithRetry(ctx, func() (*http.Request, error) { + return NewGetEverythingOverdueTodosRequest(c.Server, accountId) + }, true, "GetEverythingOverdueTodos", reqEditors...) + +} + // TrashTodo is marked as idempotent and will be retried on transient failures. func (c *Client) TrashTodo(ctx context.Context, accountId string, todoId int64, reqEditors ...RequestEditorFn) (*http.Response, error) { @@ -7819,6 +8042,62 @@ func NewCreateAttachmentRequestWithBody(server string, accountId string, params return req, nil } +// NewGetEverythingBoostsRequest generates requests for GetEverythingBoosts +func NewGetEverythingBoostsRequest(server string, accountId string, params *GetEverythingBoostsParams) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "accountId", runtime.ParamLocationPath, accountId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/%s/boosts.json", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := queryURL.Query() + + if params.Page != 0 { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "page", runtime.ParamLocationQuery, params.Page); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + // NewDeleteBoostRequest generates requests for DeleteBoost func NewDeleteBoostRequest(server string, accountId string, boostId int64) (*http.Request, error) { var err error @@ -9204,6 +9483,40 @@ func NewMoveCardColumnRequestWithBody(server string, accountId string, cardTable return req, nil } +// NewGetEverythingOverdueCardsRequest generates requests for GetEverythingOverdueCards +func NewGetEverythingOverdueCardsRequest(server string, accountId string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "accountId", runtime.ParamLocationPath, accountId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/%s/cards/overdue.json", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + // NewListMessageTypesRequest generates requests for ListMessageTypes func NewListMessageTypesRequest(server string, accountId string) (*http.Request, error) { var err error @@ -10178,8 +10491,8 @@ func NewCreateCampfireUploadRequestWithBody(server string, accountId string, cam return req, nil } -// NewListPingablePeopleRequest generates requests for ListPingablePeople -func NewListPingablePeopleRequest(server string, accountId string) (*http.Request, error) { +// NewGetEverythingCheckinsRequest generates requests for GetEverythingCheckins +func NewGetEverythingCheckinsRequest(server string, accountId string, params *GetEverythingCheckinsParams) (*http.Request, error) { var err error var pathParam0 string @@ -10194,41 +10507,7 @@ func NewListPingablePeopleRequest(server string, accountId string) (*http.Reques return nil, err } - operationPath := fmt.Sprintf("/%s/circles/people.json", pathParam0) - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest("GET", queryURL.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - -// NewListClientApprovalsRequest generates requests for ListClientApprovals -func NewListClientApprovalsRequest(server string, accountId string, params *ListClientApprovalsParams) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithLocation("simple", false, "accountId", runtime.ParamLocationPath, accountId) - if err != nil { - return nil, err - } - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/%s/client/approvals.json", pathParam0) + operationPath := fmt.Sprintf("/%s/checkins.json", pathParam0) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -10241,25 +10520,9 @@ func NewListClientApprovalsRequest(server string, accountId string, params *List if params != nil { queryValues := queryURL.Query() - if params.Sort != "" { - - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "sort", runtime.ParamLocationQuery, params.Sort); err != nil { - return nil, err - } else if parsed, err := url.ParseQuery(queryFrag); err != nil { - return nil, err - } else { - for k, v := range parsed { - for _, v2 := range v { - queryValues.Add(k, v2) - } - } - } - - } - - if params.Direction != "" { + if params.Page != 0 { - if queryFrag, err := runtime.StyleParamWithLocation("form", true, "direction", runtime.ParamLocationQuery, params.Direction); err != nil { + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "page", runtime.ParamLocationQuery, params.Page); err != nil { return nil, err } else if parsed, err := url.ParseQuery(queryFrag); err != nil { return nil, err @@ -10284,8 +10547,8 @@ func NewListClientApprovalsRequest(server string, accountId string, params *List return req, nil } -// NewGetClientApprovalRequest generates requests for GetClientApproval -func NewGetClientApprovalRequest(server string, accountId string, approvalId int64) (*http.Request, error) { +// NewListPingablePeopleRequest generates requests for ListPingablePeople +func NewListPingablePeopleRequest(server string, accountId string) (*http.Request, error) { var err error var pathParam0 string @@ -10295,19 +10558,12 @@ func NewGetClientApprovalRequest(server string, accountId string, approvalId int return nil, err } - var pathParam1 string - - pathParam1, err = runtime.StyleParamWithLocation("simple", false, "approvalId", runtime.ParamLocationPath, approvalId) - if err != nil { - return nil, err - } - serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/%s/client/approvals/%s", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/%s/circles/people.json", pathParam0) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -10325,8 +10581,8 @@ func NewGetClientApprovalRequest(server string, accountId string, approvalId int return req, nil } -// NewListClientCorrespondencesRequest generates requests for ListClientCorrespondences -func NewListClientCorrespondencesRequest(server string, accountId string, params *ListClientCorrespondencesParams) (*http.Request, error) { +// NewListClientApprovalsRequest generates requests for ListClientApprovals +func NewListClientApprovalsRequest(server string, accountId string, params *ListClientApprovalsParams) (*http.Request, error) { var err error var pathParam0 string @@ -10341,7 +10597,120 @@ func NewListClientCorrespondencesRequest(server string, accountId string, params return nil, err } - operationPath := fmt.Sprintf("/%s/client/correspondences.json", pathParam0) + operationPath := fmt.Sprintf("/%s/client/approvals.json", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := queryURL.Query() + + if params.Sort != "" { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "sort", runtime.ParamLocationQuery, params.Sort); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.Direction != "" { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "direction", runtime.ParamLocationQuery, params.Direction); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewGetClientApprovalRequest generates requests for GetClientApproval +func NewGetClientApprovalRequest(server string, accountId string, approvalId int64) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "accountId", runtime.ParamLocationPath, accountId) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "approvalId", runtime.ParamLocationPath, approvalId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/%s/client/approvals/%s", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewListClientCorrespondencesRequest generates requests for ListClientCorrespondences +func NewListClientCorrespondencesRequest(server string, accountId string, params *ListClientCorrespondencesParams) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "accountId", runtime.ParamLocationPath, accountId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/%s/client/correspondences.json", pathParam0) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -10527,6 +10896,62 @@ func NewGetClientReplyRequest(server string, accountId string, recordingId int64 return req, nil } +// NewGetEverythingCommentsRequest generates requests for GetEverythingComments +func NewGetEverythingCommentsRequest(server string, accountId string, params *GetEverythingCommentsParams) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "accountId", runtime.ParamLocationPath, accountId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/%s/comments.json", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := queryURL.Query() + + if params.Page != 0 { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "page", runtime.ParamLocationQuery, params.Page); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + // NewGetCommentRequest generates requests for GetComment func NewGetCommentRequest(server string, accountId string, commentId int64) (*http.Request, error) { var err error @@ -10853,8 +11278,8 @@ func NewUpdateDocumentRequestWithBody(server string, accountId string, documentI return req, nil } -// NewDestroyGaugeNeedleRequest generates requests for DestroyGaugeNeedle -func NewDestroyGaugeNeedleRequest(server string, accountId string, needleId int64) (*http.Request, error) { +// NewGetEverythingFilesRequest generates requests for GetEverythingFiles +func NewGetEverythingFilesRequest(server string, accountId string, params *GetEverythingFilesParams) (*http.Request, error) { var err error var pathParam0 string @@ -10864,19 +11289,12 @@ func NewDestroyGaugeNeedleRequest(server string, accountId string, needleId int6 return nil, err } - var pathParam1 string - - pathParam1, err = runtime.StyleParamWithLocation("simple", false, "needleId", runtime.ParamLocationPath, needleId) - if err != nil { - return nil, err - } - serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/%s/gauge_needles/%s", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/%s/files.json", pathParam0) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -10886,7 +11304,61 @@ func NewDestroyGaugeNeedleRequest(server string, accountId string, needleId int6 return nil, err } - req, err := http.NewRequest("DELETE", queryURL.String(), nil) + if params != nil { + queryValues := queryURL.Query() + + if params.Kind != "" { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "kind", runtime.ParamLocationQuery, params.Kind); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.PeopleIds != nil && len(*params.PeopleIds) > 0 { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "people_ids[]", runtime.ParamLocationQuery, *params.PeopleIds); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.Page != 0 { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "page", runtime.ParamLocationQuery, params.Page); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) if err != nil { return nil, err } @@ -10894,8 +11366,8 @@ func NewDestroyGaugeNeedleRequest(server string, accountId string, needleId int6 return req, nil } -// NewGetGaugeNeedleRequest generates requests for GetGaugeNeedle -func NewGetGaugeNeedleRequest(server string, accountId string, needleId int64) (*http.Request, error) { +// NewGetEverythingForwardsRequest generates requests for GetEverythingForwards +func NewGetEverythingForwardsRequest(server string, accountId string, params *GetEverythingForwardsParams) (*http.Request, error) { var err error var pathParam0 string @@ -10905,19 +11377,12 @@ func NewGetGaugeNeedleRequest(server string, accountId string, needleId int64) ( return nil, err } - var pathParam1 string - - pathParam1, err = runtime.StyleParamWithLocation("simple", false, "needleId", runtime.ParamLocationPath, needleId) - if err != nil { - return nil, err - } - serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/%s/gauge_needles/%s", pathParam0, pathParam1) + operationPath := fmt.Sprintf("/%s/forwards.json", pathParam0) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -10927,6 +11392,28 @@ func NewGetGaugeNeedleRequest(server string, accountId string, needleId int64) ( return nil, err } + if params != nil { + queryValues := queryURL.Query() + + if params.Page != 0 { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "page", runtime.ParamLocationQuery, params.Page); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() + } + req, err := http.NewRequest("GET", queryURL.String(), nil) if err != nil { return nil, err @@ -10935,19 +11422,101 @@ func NewGetGaugeNeedleRequest(server string, accountId string, needleId int64) ( return req, nil } -// NewUpdateGaugeNeedleRequest calls the generic UpdateGaugeNeedle builder with application/json body -func NewUpdateGaugeNeedleRequest(server string, accountId string, needleId int64, body UpdateGaugeNeedleJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewUpdateGaugeNeedleRequestWithBody(server, accountId, needleId, "application/json", bodyReader) -} - -// NewUpdateGaugeNeedleRequestWithBody generates requests for UpdateGaugeNeedle with any type of body -func NewUpdateGaugeNeedleRequestWithBody(server string, accountId string, needleId int64, contentType string, body io.Reader) (*http.Request, error) { +// NewDestroyGaugeNeedleRequest generates requests for DestroyGaugeNeedle +func NewDestroyGaugeNeedleRequest(server string, accountId string, needleId int64) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "accountId", runtime.ParamLocationPath, accountId) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "needleId", runtime.ParamLocationPath, needleId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/%s/gauge_needles/%s", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("DELETE", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewGetGaugeNeedleRequest generates requests for GetGaugeNeedle +func NewGetGaugeNeedleRequest(server string, accountId string, needleId int64) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "accountId", runtime.ParamLocationPath, accountId) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "needleId", runtime.ParamLocationPath, needleId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/%s/gauge_needles/%s", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewUpdateGaugeNeedleRequest calls the generic UpdateGaugeNeedle builder with application/json body +func NewUpdateGaugeNeedleRequest(server string, accountId string, needleId int64, body UpdateGaugeNeedleJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewUpdateGaugeNeedleRequestWithBody(server, accountId, needleId, "application/json", bodyReader) +} + +// NewUpdateGaugeNeedleRequestWithBody generates requests for UpdateGaugeNeedle with any type of body +func NewUpdateGaugeNeedleRequestWithBody(server string, accountId string, needleId int64, contentType string, body io.Reader) (*http.Request, error) { var err error var pathParam0 string @@ -11643,6 +12212,62 @@ func NewCreateMessageRequestWithBody(server string, accountId string, boardId in return req, nil } +// NewGetEverythingMessagesRequest generates requests for GetEverythingMessages +func NewGetEverythingMessagesRequest(server string, accountId string, params *GetEverythingMessagesParams) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "accountId", runtime.ParamLocationPath, accountId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/%s/messages.json", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := queryURL.Query() + + if params.Page != 0 { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "page", runtime.ParamLocationQuery, params.Page); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + // NewGetMessageRequest generates requests for GetMessage func NewGetMessageRequest(server string, accountId string, messageId int64) (*http.Request, error) { var err error @@ -16763,6 +17388,40 @@ func NewCreateTodoRequestWithBody(server string, accountId string, todolistId in return req, nil } +// NewGetEverythingOverdueTodosRequest generates requests for GetEverythingOverdueTodos +func NewGetEverythingOverdueTodosRequest(server string, accountId string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "accountId", runtime.ParamLocationPath, accountId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/%s/todos/overdue.json", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + // NewTrashTodoRequest generates requests for TrashTodo func NewTrashTodoRequest(server string, accountId string, todoId int64) (*http.Request, error) { var err error @@ -18025,6 +18684,7 @@ var operationMetadata = map[string]OperationMetadata{ "UpdateAccountLogo": {Idempotent: true, HasSensitiveParams: false}, "UpdateAccountName": {Idempotent: true, HasSensitiveParams: false}, "CreateAttachment": {Idempotent: false, HasSensitiveParams: false}, + "GetEverythingBoosts": {Idempotent: true, HasSensitiveParams: false}, "DeleteBoost": {Idempotent: true, HasSensitiveParams: false}, "GetBoost": {Idempotent: true, HasSensitiveParams: false}, "SetCardColumnColor": {Idempotent: true, HasSensitiveParams: false}, @@ -18053,6 +18713,7 @@ var operationMetadata = map[string]OperationMetadata{ "GetCardTable": {Idempotent: true, HasSensitiveParams: false}, "CreateCardColumn": {Idempotent: false, HasSensitiveParams: false}, "MoveCardColumn": {Idempotent: false, HasSensitiveParams: false}, + "GetEverythingOverdueCards": {Idempotent: true, HasSensitiveParams: false}, "ListMessageTypes": {Idempotent: true, HasSensitiveParams: false}, "CreateMessageType": {Idempotent: false, HasSensitiveParams: false}, "DeleteMessageType": {Idempotent: true, HasSensitiveParams: false}, @@ -18072,6 +18733,7 @@ var operationMetadata = map[string]OperationMetadata{ "UpdateCampfireLine": {Idempotent: true, HasSensitiveParams: false}, "ListCampfireUploads": {Idempotent: true, HasSensitiveParams: false}, "CreateCampfireUpload": {Idempotent: false, HasSensitiveParams: false}, + "GetEverythingCheckins": {Idempotent: true, HasSensitiveParams: false}, "ListPingablePeople": {Idempotent: true, HasSensitiveParams: false}, "ListClientApprovals": {Idempotent: true, HasSensitiveParams: false}, "GetClientApproval": {Idempotent: true, HasSensitiveParams: false}, @@ -18079,6 +18741,7 @@ var operationMetadata = map[string]OperationMetadata{ "GetClientCorrespondence": {Idempotent: true, HasSensitiveParams: false}, "ListClientReplies": {Idempotent: true, HasSensitiveParams: false}, "GetClientReply": {Idempotent: true, HasSensitiveParams: false}, + "GetEverythingComments": {Idempotent: true, HasSensitiveParams: false}, "GetComment": {Idempotent: true, HasSensitiveParams: false}, "UpdateComment": {Idempotent: true, HasSensitiveParams: false}, "DeleteTool": {Idempotent: true, HasSensitiveParams: false}, @@ -18086,6 +18749,8 @@ var operationMetadata = map[string]OperationMetadata{ "UpdateTool": {Idempotent: true, HasSensitiveParams: false}, "GetDocument": {Idempotent: true, HasSensitiveParams: false}, "UpdateDocument": {Idempotent: true, HasSensitiveParams: false}, + "GetEverythingFiles": {Idempotent: true, HasSensitiveParams: false}, + "GetEverythingForwards": {Idempotent: true, HasSensitiveParams: false}, "DestroyGaugeNeedle": {Idempotent: true, HasSensitiveParams: false}, "GetGaugeNeedle": {Idempotent: true, HasSensitiveParams: false}, "UpdateGaugeNeedle": {Idempotent: true, HasSensitiveParams: false}, @@ -18102,6 +18767,7 @@ var operationMetadata = map[string]OperationMetadata{ "GetMessageBoard": {Idempotent: true, HasSensitiveParams: false}, "ListMessages": {Idempotent: true, HasSensitiveParams: false}, "CreateMessage": {Idempotent: false, HasSensitiveParams: false}, + "GetEverythingMessages": {Idempotent: true, HasSensitiveParams: false}, "GetMessage": {Idempotent: true, HasSensitiveParams: false}, "UpdateMessage": {Idempotent: true, HasSensitiveParams: false}, "GetMyAssignments": {Idempotent: true, HasSensitiveParams: false}, @@ -18203,6 +18869,7 @@ var operationMetadata = map[string]OperationMetadata{ "CreateTodolistGroup": {Idempotent: false, HasSensitiveParams: false}, "ListTodos": {Idempotent: true, HasSensitiveParams: false}, "CreateTodo": {Idempotent: false, HasSensitiveParams: false}, + "GetEverythingOverdueTodos": {Idempotent: true, HasSensitiveParams: false}, "TrashTodo": {Idempotent: true, HasSensitiveParams: false}, "GetTodo": {Idempotent: true, HasSensitiveParams: false}, "ReplaceTodo": {Idempotent: true, HasSensitiveParams: false}, @@ -19181,6 +19848,9 @@ type ClientWithResponsesInterface interface { // CreateAttachmentWithBodyWithResponse request with any body CreateAttachmentWithBodyWithResponse(ctx context.Context, accountId string, params *CreateAttachmentParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateAttachmentResponse, error) + // GetEverythingBoostsWithResponse request + GetEverythingBoostsWithResponse(ctx context.Context, accountId string, params *GetEverythingBoostsParams, reqEditors ...RequestEditorFn) (*GetEverythingBoostsResponse, error) + // DeleteBoostWithResponse request DeleteBoostWithResponse(ctx context.Context, accountId string, boostId int64, reqEditors ...RequestEditorFn) (*DeleteBoostResponse, error) @@ -19295,6 +19965,9 @@ type ClientWithResponsesInterface interface { MoveCardColumnWithResponse(ctx context.Context, accountId string, cardTableId int64, body MoveCardColumnJSONRequestBody, reqEditors ...RequestEditorFn) (*MoveCardColumnResponse, error) + // GetEverythingOverdueCardsWithResponse request + GetEverythingOverdueCardsWithResponse(ctx context.Context, accountId string, reqEditors ...RequestEditorFn) (*GetEverythingOverdueCardsResponse, error) + // ListMessageTypesWithResponse request ListMessageTypesWithResponse(ctx context.Context, accountId string, reqEditors ...RequestEditorFn) (*ListMessageTypesResponse, error) @@ -19364,6 +20037,9 @@ type ClientWithResponsesInterface interface { // CreateCampfireUploadWithBodyWithResponse request with any body CreateCampfireUploadWithBodyWithResponse(ctx context.Context, accountId string, campfireId int64, params *CreateCampfireUploadParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateCampfireUploadResponse, error) + // GetEverythingCheckinsWithResponse request + GetEverythingCheckinsWithResponse(ctx context.Context, accountId string, params *GetEverythingCheckinsParams, reqEditors ...RequestEditorFn) (*GetEverythingCheckinsResponse, error) + // ListPingablePeopleWithResponse request ListPingablePeopleWithResponse(ctx context.Context, accountId string, reqEditors ...RequestEditorFn) (*ListPingablePeopleResponse, error) @@ -19385,6 +20061,9 @@ type ClientWithResponsesInterface interface { // GetClientReplyWithResponse request GetClientReplyWithResponse(ctx context.Context, accountId string, recordingId int64, replyId int64, reqEditors ...RequestEditorFn) (*GetClientReplyResponse, error) + // GetEverythingCommentsWithResponse request + GetEverythingCommentsWithResponse(ctx context.Context, accountId string, params *GetEverythingCommentsParams, reqEditors ...RequestEditorFn) (*GetEverythingCommentsResponse, error) + // GetCommentWithResponse request GetCommentWithResponse(ctx context.Context, accountId string, commentId int64, reqEditors ...RequestEditorFn) (*GetCommentResponse, error) @@ -19412,6 +20091,12 @@ type ClientWithResponsesInterface interface { UpdateDocumentWithResponse(ctx context.Context, accountId string, documentId int64, body UpdateDocumentJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateDocumentResponse, error) + // GetEverythingFilesWithResponse request + GetEverythingFilesWithResponse(ctx context.Context, accountId string, params *GetEverythingFilesParams, reqEditors ...RequestEditorFn) (*GetEverythingFilesResponse, error) + + // GetEverythingForwardsWithResponse request + GetEverythingForwardsWithResponse(ctx context.Context, accountId string, params *GetEverythingForwardsParams, reqEditors ...RequestEditorFn) (*GetEverythingForwardsResponse, error) + // DestroyGaugeNeedleWithResponse request DestroyGaugeNeedleWithResponse(ctx context.Context, accountId string, needleId int64, reqEditors ...RequestEditorFn) (*DestroyGaugeNeedleResponse, error) @@ -19470,6 +20155,9 @@ type ClientWithResponsesInterface interface { CreateMessageWithResponse(ctx context.Context, accountId string, boardId int64, body CreateMessageJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateMessageResponse, error) + // GetEverythingMessagesWithResponse request + GetEverythingMessagesWithResponse(ctx context.Context, accountId string, params *GetEverythingMessagesParams, reqEditors ...RequestEditorFn) (*GetEverythingMessagesResponse, error) + // GetMessageWithResponse request GetMessageWithResponse(ctx context.Context, accountId string, messageId int64, reqEditors ...RequestEditorFn) (*GetMessageResponse, error) @@ -19839,6 +20527,9 @@ type ClientWithResponsesInterface interface { CreateTodoWithResponse(ctx context.Context, accountId string, todolistId int64, body CreateTodoJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateTodoResponse, error) + // GetEverythingOverdueTodosWithResponse request + GetEverythingOverdueTodosWithResponse(ctx context.Context, accountId string, reqEditors ...RequestEditorFn) (*GetEverythingOverdueTodosResponse, error) + // TrashTodoWithResponse request TrashTodoWithResponse(ctx context.Context, accountId string, todoId int64, reqEditors ...RequestEditorFn) (*TrashTodoResponse, error) @@ -20110,6 +20801,40 @@ func (r CreateAttachmentResponse) ContentType() string { return "" } +type GetEverythingBoostsResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *GetEverythingBoostsResponseContent + JSON401 *UnauthorizedErrorResponseContent + JSON403 *ForbiddenErrorResponseContent + JSON429 *RateLimitErrorResponseContent + JSON500 *InternalServerErrorResponseContent +} + +// Status returns HTTPResponse.Status +func (r GetEverythingBoostsResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetEverythingBoostsResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers +func (r GetEverythingBoostsResponse) ContentType() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Header.Get("Content-Type") + } + return "" +} + type DeleteBoostResponse struct { Body []byte HTTPResponse *http.Response @@ -21076,6 +21801,39 @@ func (r MoveCardColumnResponse) ContentType() string { return "" } +type GetEverythingOverdueCardsResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *GetEverythingOverdueCardsResponseContent + JSON401 *UnauthorizedErrorResponseContent + JSON403 *ForbiddenErrorResponseContent + JSON500 *InternalServerErrorResponseContent +} + +// Status returns HTTPResponse.Status +func (r GetEverythingOverdueCardsResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetEverythingOverdueCardsResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers +func (r GetEverythingOverdueCardsResponse) ContentType() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Header.Get("Content-Type") + } + return "" +} + type ListMessageTypesResponse struct { Body []byte HTTPResponse *http.Response @@ -21726,6 +22484,40 @@ func (r CreateCampfireUploadResponse) ContentType() string { return "" } +type GetEverythingCheckinsResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *GetEverythingCheckinsResponseContent + JSON401 *UnauthorizedErrorResponseContent + JSON403 *ForbiddenErrorResponseContent + JSON429 *RateLimitErrorResponseContent + JSON500 *InternalServerErrorResponseContent +} + +// Status returns HTTPResponse.Status +func (r GetEverythingCheckinsResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetEverythingCheckinsResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers +func (r GetEverythingCheckinsResponse) ContentType() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Header.Get("Content-Type") + } + return "" +} + type ListPingablePeopleResponse struct { Body []byte HTTPResponse *http.Response @@ -21964,6 +22756,40 @@ func (r GetClientReplyResponse) ContentType() string { return "" } +type GetEverythingCommentsResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *GetEverythingCommentsResponseContent + JSON401 *UnauthorizedErrorResponseContent + JSON403 *ForbiddenErrorResponseContent + JSON429 *RateLimitErrorResponseContent + JSON500 *InternalServerErrorResponseContent +} + +// Status returns HTTPResponse.Status +func (r GetEverythingCommentsResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetEverythingCommentsResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers +func (r GetEverythingCommentsResponse) ContentType() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Header.Get("Content-Type") + } + return "" +} + type GetCommentResponse struct { Body []byte HTTPResponse *http.Response @@ -22204,6 +23030,74 @@ func (r UpdateDocumentResponse) ContentType() string { return "" } +type GetEverythingFilesResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *GetEverythingFilesResponseContent + JSON401 *UnauthorizedErrorResponseContent + JSON403 *ForbiddenErrorResponseContent + JSON429 *RateLimitErrorResponseContent + JSON500 *InternalServerErrorResponseContent +} + +// Status returns HTTPResponse.Status +func (r GetEverythingFilesResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetEverythingFilesResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers +func (r GetEverythingFilesResponse) ContentType() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Header.Get("Content-Type") + } + return "" +} + +type GetEverythingForwardsResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *GetEverythingForwardsResponseContent + JSON401 *UnauthorizedErrorResponseContent + JSON403 *ForbiddenErrorResponseContent + JSON429 *RateLimitErrorResponseContent + JSON500 *InternalServerErrorResponseContent +} + +// Status returns HTTPResponse.Status +func (r GetEverythingForwardsResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetEverythingForwardsResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers +func (r GetEverythingForwardsResponse) ContentType() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Header.Get("Content-Type") + } + return "" +} + type DestroyGaugeNeedleResponse struct { Body []byte HTTPResponse *http.Response @@ -22751,6 +23645,40 @@ func (r CreateMessageResponse) ContentType() string { return "" } +type GetEverythingMessagesResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *GetEverythingMessagesResponseContent + JSON401 *UnauthorizedErrorResponseContent + JSON403 *ForbiddenErrorResponseContent + JSON429 *RateLimitErrorResponseContent + JSON500 *InternalServerErrorResponseContent +} + +// Status returns HTTPResponse.Status +func (r GetEverythingMessagesResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetEverythingMessagesResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers +func (r GetEverythingMessagesResponse) ContentType() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Header.Get("Content-Type") + } + return "" +} + type GetMessageResponse struct { Body []byte HTTPResponse *http.Response @@ -26208,6 +27136,39 @@ func (r CreateTodoResponse) ContentType() string { return "" } +type GetEverythingOverdueTodosResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *GetEverythingOverdueTodosResponseContent + JSON401 *UnauthorizedErrorResponseContent + JSON403 *ForbiddenErrorResponseContent + JSON500 *InternalServerErrorResponseContent +} + +// Status returns HTTPResponse.Status +func (r GetEverythingOverdueTodosResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetEverythingOverdueTodosResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers +func (r GetEverythingOverdueTodosResponse) ContentType() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Header.Get("Content-Type") + } + return "" +} + type TrashTodoResponse struct { Body []byte HTTPResponse *http.Response @@ -27152,6 +28113,15 @@ func (c *ClientWithResponses) CreateAttachmentWithBodyWithResponse(ctx context.C return ParseCreateAttachmentResponse(rsp) } +// GetEverythingBoostsWithResponse request returning *GetEverythingBoostsResponse +func (c *ClientWithResponses) GetEverythingBoostsWithResponse(ctx context.Context, accountId string, params *GetEverythingBoostsParams, reqEditors ...RequestEditorFn) (*GetEverythingBoostsResponse, error) { + rsp, err := c.GetEverythingBoosts(ctx, accountId, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetEverythingBoostsResponse(rsp) +} + // DeleteBoostWithResponse request returning *DeleteBoostResponse func (c *ClientWithResponses) DeleteBoostWithResponse(ctx context.Context, accountId string, boostId int64, reqEditors ...RequestEditorFn) (*DeleteBoostResponse, error) { rsp, err := c.DeleteBoost(ctx, accountId, boostId, reqEditors...) @@ -27524,6 +28494,15 @@ func (c *ClientWithResponses) MoveCardColumnWithResponse(ctx context.Context, ac return ParseMoveCardColumnResponse(rsp) } +// GetEverythingOverdueCardsWithResponse request returning *GetEverythingOverdueCardsResponse +func (c *ClientWithResponses) GetEverythingOverdueCardsWithResponse(ctx context.Context, accountId string, reqEditors ...RequestEditorFn) (*GetEverythingOverdueCardsResponse, error) { + rsp, err := c.GetEverythingOverdueCards(ctx, accountId, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetEverythingOverdueCardsResponse(rsp) +} + // ListMessageTypesWithResponse request returning *ListMessageTypesResponse func (c *ClientWithResponses) ListMessageTypesWithResponse(ctx context.Context, accountId string, reqEditors ...RequestEditorFn) (*ListMessageTypesResponse, error) { rsp, err := c.ListMessageTypes(ctx, accountId, reqEditors...) @@ -27743,6 +28722,15 @@ func (c *ClientWithResponses) CreateCampfireUploadWithBodyWithResponse(ctx conte return ParseCreateCampfireUploadResponse(rsp) } +// GetEverythingCheckinsWithResponse request returning *GetEverythingCheckinsResponse +func (c *ClientWithResponses) GetEverythingCheckinsWithResponse(ctx context.Context, accountId string, params *GetEverythingCheckinsParams, reqEditors ...RequestEditorFn) (*GetEverythingCheckinsResponse, error) { + rsp, err := c.GetEverythingCheckins(ctx, accountId, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetEverythingCheckinsResponse(rsp) +} + // ListPingablePeopleWithResponse request returning *ListPingablePeopleResponse func (c *ClientWithResponses) ListPingablePeopleWithResponse(ctx context.Context, accountId string, reqEditors ...RequestEditorFn) (*ListPingablePeopleResponse, error) { rsp, err := c.ListPingablePeople(ctx, accountId, reqEditors...) @@ -27806,6 +28794,15 @@ func (c *ClientWithResponses) GetClientReplyWithResponse(ctx context.Context, ac return ParseGetClientReplyResponse(rsp) } +// GetEverythingCommentsWithResponse request returning *GetEverythingCommentsResponse +func (c *ClientWithResponses) GetEverythingCommentsWithResponse(ctx context.Context, accountId string, params *GetEverythingCommentsParams, reqEditors ...RequestEditorFn) (*GetEverythingCommentsResponse, error) { + rsp, err := c.GetEverythingComments(ctx, accountId, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetEverythingCommentsResponse(rsp) +} + // GetCommentWithResponse request returning *GetCommentResponse func (c *ClientWithResponses) GetCommentWithResponse(ctx context.Context, accountId string, commentId int64, reqEditors ...RequestEditorFn) (*GetCommentResponse, error) { rsp, err := c.GetComment(ctx, accountId, commentId, reqEditors...) @@ -27893,6 +28890,24 @@ func (c *ClientWithResponses) UpdateDocumentWithResponse(ctx context.Context, ac return ParseUpdateDocumentResponse(rsp) } +// GetEverythingFilesWithResponse request returning *GetEverythingFilesResponse +func (c *ClientWithResponses) GetEverythingFilesWithResponse(ctx context.Context, accountId string, params *GetEverythingFilesParams, reqEditors ...RequestEditorFn) (*GetEverythingFilesResponse, error) { + rsp, err := c.GetEverythingFiles(ctx, accountId, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetEverythingFilesResponse(rsp) +} + +// GetEverythingForwardsWithResponse request returning *GetEverythingForwardsResponse +func (c *ClientWithResponses) GetEverythingForwardsWithResponse(ctx context.Context, accountId string, params *GetEverythingForwardsParams, reqEditors ...RequestEditorFn) (*GetEverythingForwardsResponse, error) { + rsp, err := c.GetEverythingForwards(ctx, accountId, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetEverythingForwardsResponse(rsp) +} + // DestroyGaugeNeedleWithResponse request returning *DestroyGaugeNeedleResponse func (c *ClientWithResponses) DestroyGaugeNeedleWithResponse(ctx context.Context, accountId string, needleId int64, reqEditors ...RequestEditorFn) (*DestroyGaugeNeedleResponse, error) { rsp, err := c.DestroyGaugeNeedle(ctx, accountId, needleId, reqEditors...) @@ -28077,6 +29092,15 @@ func (c *ClientWithResponses) CreateMessageWithResponse(ctx context.Context, acc return ParseCreateMessageResponse(rsp) } +// GetEverythingMessagesWithResponse request returning *GetEverythingMessagesResponse +func (c *ClientWithResponses) GetEverythingMessagesWithResponse(ctx context.Context, accountId string, params *GetEverythingMessagesParams, reqEditors ...RequestEditorFn) (*GetEverythingMessagesResponse, error) { + rsp, err := c.GetEverythingMessages(ctx, accountId, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetEverythingMessagesResponse(rsp) +} + // GetMessageWithResponse request returning *GetMessageResponse func (c *ClientWithResponses) GetMessageWithResponse(ctx context.Context, accountId string, messageId int64, reqEditors ...RequestEditorFn) (*GetMessageResponse, error) { rsp, err := c.GetMessage(ctx, accountId, messageId, reqEditors...) @@ -29250,6 +30274,15 @@ func (c *ClientWithResponses) CreateTodoWithResponse(ctx context.Context, accoun return ParseCreateTodoResponse(rsp) } +// GetEverythingOverdueTodosWithResponse request returning *GetEverythingOverdueTodosResponse +func (c *ClientWithResponses) GetEverythingOverdueTodosWithResponse(ctx context.Context, accountId string, reqEditors ...RequestEditorFn) (*GetEverythingOverdueTodosResponse, error) { + rsp, err := c.GetEverythingOverdueTodos(ctx, accountId, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetEverythingOverdueTodosResponse(rsp) +} + // TrashTodoWithResponse request returning *TrashTodoResponse func (c *ClientWithResponses) TrashTodoWithResponse(ctx context.Context, accountId string, todoId int64, reqEditors ...RequestEditorFn) (*TrashTodoResponse, error) { rsp, err := c.TrashTodo(ctx, accountId, todoId, reqEditors...) @@ -29669,15 +30702,248 @@ func ParseRemoveAccountLogoResponse(rsp *http.Response) (*RemoveAccountLogoRespo return response, nil } -// ParseUpdateAccountLogoResponse parses an HTTP response from a UpdateAccountLogoWithResponse call -func ParseUpdateAccountLogoResponse(rsp *http.Response) (*UpdateAccountLogoResponse, error) { +// ParseUpdateAccountLogoResponse parses an HTTP response from a UpdateAccountLogoWithResponse call +func ParseUpdateAccountLogoResponse(rsp *http.Response) (*UpdateAccountLogoResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &UpdateAccountLogoResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case rsp.StatusCode == 204: + break // No content-type + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: + var dest UnauthorizedErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON401 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: + var dest ForbiddenErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON403 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 422: + var dest ValidationErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON422 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 429: + var dest RateLimitErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON429 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest InternalServerErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + +// ParseUpdateAccountNameResponse parses an HTTP response from a UpdateAccountNameWithResponse call +func ParseUpdateAccountNameResponse(rsp *http.Response) (*UpdateAccountNameResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &UpdateAccountNameResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest UpdateAccountNameResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest BadRequestErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: + var dest UnauthorizedErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON401 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: + var dest ForbiddenErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON403 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 429: + var dest RateLimitErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON429 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest InternalServerErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + +// ParseCreateAttachmentResponse parses an HTTP response from a CreateAttachmentWithResponse call +func ParseCreateAttachmentResponse(rsp *http.Response) (*CreateAttachmentResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &CreateAttachmentResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest CreateAttachmentResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON201 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: + var dest UnauthorizedErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON401 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: + var dest ForbiddenErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON403 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 422: + var dest ValidationErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON422 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 429: + var dest RateLimitErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON429 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest InternalServerErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + +// ParseGetEverythingBoostsResponse parses an HTTP response from a GetEverythingBoostsWithResponse call +func ParseGetEverythingBoostsResponse(rsp *http.Response) (*GetEverythingBoostsResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetEverythingBoostsResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest GetEverythingBoostsResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: + var dest UnauthorizedErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON401 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: + var dest ForbiddenErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON403 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 429: + var dest RateLimitErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON429 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest InternalServerErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + +// ParseDeleteBoostResponse parses an HTTP response from a DeleteBoostWithResponse call +func ParseDeleteBoostResponse(rsp *http.Response) (*DeleteBoostResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &UpdateAccountLogoResponse{ + response := &DeleteBoostResponse{ Body: bodyBytes, HTTPResponse: rsp, } @@ -29700,6 +30966,121 @@ func ParseUpdateAccountLogoResponse(rsp *http.Response) (*UpdateAccountLogoRespo } response.JSON403 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest NotFoundErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest InternalServerErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + +// ParseGetBoostResponse parses an HTTP response from a GetBoostWithResponse call +func ParseGetBoostResponse(rsp *http.Response) (*GetBoostResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetBoostResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest GetBoostResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: + var dest UnauthorizedErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON401 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: + var dest ForbiddenErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON403 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest NotFoundErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest InternalServerErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + +// ParseSetCardColumnColorResponse parses an HTTP response from a SetCardColumnColorWithResponse call +func ParseSetCardColumnColorResponse(rsp *http.Response) (*SetCardColumnColorResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &SetCardColumnColorResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest SetCardColumnColorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: + var dest UnauthorizedErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON401 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: + var dest ForbiddenErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON403 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest NotFoundErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 422: var dest ValidationErrorResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { @@ -29726,34 +31107,27 @@ func ParseUpdateAccountLogoResponse(rsp *http.Response) (*UpdateAccountLogoRespo return response, nil } -// ParseUpdateAccountNameResponse parses an HTTP response from a UpdateAccountNameWithResponse call -func ParseUpdateAccountNameResponse(rsp *http.Response) (*UpdateAccountNameResponse, error) { +// ParseDisableCardColumnOnHoldResponse parses an HTTP response from a DisableCardColumnOnHoldWithResponse call +func ParseDisableCardColumnOnHoldResponse(rsp *http.Response) (*DisableCardColumnOnHoldResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &UpdateAccountNameResponse{ + response := &DisableCardColumnOnHoldResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest UpdateAccountNameResponseContent + var dest DisableCardColumnOnHoldResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } response.JSON200 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: - var dest BadRequestErrorResponseContent - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON400 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: var dest UnauthorizedErrorResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { @@ -29768,6 +31142,13 @@ func ParseUpdateAccountNameResponse(rsp *http.Response) (*UpdateAccountNameRespo } response.JSON403 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest NotFoundErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 429: var dest RateLimitErrorResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { @@ -29787,26 +31168,26 @@ func ParseUpdateAccountNameResponse(rsp *http.Response) (*UpdateAccountNameRespo return response, nil } -// ParseCreateAttachmentResponse parses an HTTP response from a CreateAttachmentWithResponse call -func ParseCreateAttachmentResponse(rsp *http.Response) (*CreateAttachmentResponse, error) { +// ParseEnableCardColumnOnHoldResponse parses an HTTP response from a EnableCardColumnOnHoldWithResponse call +func ParseEnableCardColumnOnHoldResponse(rsp *http.Response) (*EnableCardColumnOnHoldResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &CreateAttachmentResponse{ + response := &EnableCardColumnOnHoldResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: - var dest CreateAttachmentResponseContent + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest EnableCardColumnOnHoldResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON201 = &dest + response.JSON200 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: var dest UnauthorizedErrorResponseContent @@ -29848,15 +31229,15 @@ func ParseCreateAttachmentResponse(rsp *http.Response) (*CreateAttachmentRespons return response, nil } -// ParseDeleteBoostResponse parses an HTTP response from a DeleteBoostWithResponse call -func ParseDeleteBoostResponse(rsp *http.Response) (*DeleteBoostResponse, error) { +// ParseDeleteWormholeResponse parses an HTTP response from a DeleteWormholeWithResponse call +func ParseDeleteWormholeResponse(rsp *http.Response) (*DeleteWormholeResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &DeleteBoostResponse{ + response := &DeleteWormholeResponse{ Body: bodyBytes, HTTPResponse: rsp, } @@ -29886,6 +31267,13 @@ func ParseDeleteBoostResponse(rsp *http.Response) (*DeleteBoostResponse, error) } response.JSON404 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 429: + var dest RateLimitErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON429 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: var dest InternalServerErrorResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { @@ -29898,22 +31286,22 @@ func ParseDeleteBoostResponse(rsp *http.Response) (*DeleteBoostResponse, error) return response, nil } -// ParseGetBoostResponse parses an HTTP response from a GetBoostWithResponse call -func ParseGetBoostResponse(rsp *http.Response) (*GetBoostResponse, error) { +// ParseUpdateWormholeResponse parses an HTTP response from a UpdateWormholeWithResponse call +func ParseUpdateWormholeResponse(rsp *http.Response) (*UpdateWormholeResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetBoostResponse{ + response := &UpdateWormholeResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest GetBoostResponseContent + var dest UpdateWormholeResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -29952,26 +31340,26 @@ func ParseGetBoostResponse(rsp *http.Response) (*GetBoostResponse, error) { return response, nil } -// ParseSetCardColumnColorResponse parses an HTTP response from a SetCardColumnColorWithResponse call -func ParseSetCardColumnColorResponse(rsp *http.Response) (*SetCardColumnColorResponse, error) { +// ParseCreateWormholeResponse parses an HTTP response from a CreateWormholeWithResponse call +func ParseCreateWormholeResponse(rsp *http.Response) (*CreateWormholeResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &SetCardColumnColorResponse{ + response := &CreateWormholeResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest SetCardColumnColorResponseContent + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest CreateWormholeResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON200 = &dest + response.JSON201 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: var dest UnauthorizedErrorResponseContent @@ -30020,26 +31408,26 @@ func ParseSetCardColumnColorResponse(rsp *http.Response) (*SetCardColumnColorRes return response, nil } -// ParseDisableCardColumnOnHoldResponse parses an HTTP response from a DisableCardColumnOnHoldWithResponse call -func ParseDisableCardColumnOnHoldResponse(rsp *http.Response) (*DisableCardColumnOnHoldResponse, error) { +// ParseCreateToolResponse parses an HTTP response from a CreateToolWithResponse call +func ParseCreateToolResponse(rsp *http.Response) (*CreateToolResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &DisableCardColumnOnHoldResponse{ + response := &CreateToolResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest DisableCardColumnOnHoldResponseContent + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest CreateToolResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON200 = &dest + response.JSON201 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: var dest UnauthorizedErrorResponseContent @@ -30055,12 +31443,12 @@ func ParseDisableCardColumnOnHoldResponse(rsp *http.Response) (*DisableCardColum } response.JSON403 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: - var dest NotFoundErrorResponseContent + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 422: + var dest ValidationErrorResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON404 = &dest + response.JSON422 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 429: var dest RateLimitErrorResponseContent @@ -30081,22 +31469,22 @@ func ParseDisableCardColumnOnHoldResponse(rsp *http.Response) (*DisableCardColum return response, nil } -// ParseEnableCardColumnOnHoldResponse parses an HTTP response from a EnableCardColumnOnHoldWithResponse call -func ParseEnableCardColumnOnHoldResponse(rsp *http.Response) (*EnableCardColumnOnHoldResponse, error) { +// ParseListWebhooksResponse parses an HTTP response from a ListWebhooksWithResponse call +func ParseListWebhooksResponse(rsp *http.Response) (*ListWebhooksResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &EnableCardColumnOnHoldResponse{ + response := &ListWebhooksResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest EnableCardColumnOnHoldResponseContent + var dest ListWebhooksResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -30116,13 +31504,6 @@ func ParseEnableCardColumnOnHoldResponse(rsp *http.Response) (*EnableCardColumnO } response.JSON403 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 422: - var dest ValidationErrorResponseContent - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON422 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 429: var dest RateLimitErrorResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { @@ -30142,22 +31523,33 @@ func ParseEnableCardColumnOnHoldResponse(rsp *http.Response) (*EnableCardColumnO return response, nil } -// ParseDeleteWormholeResponse parses an HTTP response from a DeleteWormholeWithResponse call -func ParseDeleteWormholeResponse(rsp *http.Response) (*DeleteWormholeResponse, error) { +// ParseCreateWebhookResponse parses an HTTP response from a CreateWebhookWithResponse call +func ParseCreateWebhookResponse(rsp *http.Response) (*CreateWebhookResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &DeleteWormholeResponse{ + response := &CreateWebhookResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case rsp.StatusCode == 204: - break // No content-type + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest CreateWebhookResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON201 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest BadRequestErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: var dest UnauthorizedErrorResponseContent @@ -30173,13 +31565,6 @@ func ParseDeleteWormholeResponse(rsp *http.Response) (*DeleteWormholeResponse, e } response.JSON403 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: - var dest NotFoundErrorResponseContent - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON404 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 429: var dest RateLimitErrorResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { @@ -30194,27 +31579,34 @@ func ParseDeleteWormholeResponse(rsp *http.Response) (*DeleteWormholeResponse, e } response.JSON500 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 507: + var dest WebhookLimitErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON507 = &dest + } return response, nil } -// ParseUpdateWormholeResponse parses an HTTP response from a UpdateWormholeWithResponse call -func ParseUpdateWormholeResponse(rsp *http.Response) (*UpdateWormholeResponse, error) { +// ParseGetCardResponse parses an HTTP response from a GetCardWithResponse call +func ParseGetCardResponse(rsp *http.Response) (*GetCardResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &UpdateWormholeResponse{ + response := &GetCardResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest UpdateWormholeResponseContent + var dest GetCardResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -30253,26 +31645,26 @@ func ParseUpdateWormholeResponse(rsp *http.Response) (*UpdateWormholeResponse, e return response, nil } -// ParseCreateWormholeResponse parses an HTTP response from a CreateWormholeWithResponse call -func ParseCreateWormholeResponse(rsp *http.Response) (*CreateWormholeResponse, error) { +// ParseUpdateCardResponse parses an HTTP response from a UpdateCardWithResponse call +func ParseUpdateCardResponse(rsp *http.Response) (*UpdateCardResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &CreateWormholeResponse{ + response := &UpdateCardResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: - var dest CreateWormholeResponseContent + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest UpdateCardResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON201 = &dest + response.JSON200 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: var dest UnauthorizedErrorResponseContent @@ -30302,13 +31694,6 @@ func ParseCreateWormholeResponse(rsp *http.Response) (*CreateWormholeResponse, e } response.JSON422 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 429: - var dest RateLimitErrorResponseContent - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON429 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: var dest InternalServerErrorResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { @@ -30321,26 +31706,22 @@ func ParseCreateWormholeResponse(rsp *http.Response) (*CreateWormholeResponse, e return response, nil } -// ParseCreateToolResponse parses an HTTP response from a CreateToolWithResponse call -func ParseCreateToolResponse(rsp *http.Response) (*CreateToolResponse, error) { +// ParseMoveCardResponse parses an HTTP response from a MoveCardWithResponse call +func ParseMoveCardResponse(rsp *http.Response) (*MoveCardResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &CreateToolResponse{ + response := &MoveCardResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: - var dest CreateToolResponseContent - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON201 = &dest + case rsp.StatusCode == 204: + break // No content-type case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: var dest UnauthorizedErrorResponseContent @@ -30382,26 +31763,22 @@ func ParseCreateToolResponse(rsp *http.Response) (*CreateToolResponse, error) { return response, nil } -// ParseListWebhooksResponse parses an HTTP response from a ListWebhooksWithResponse call -func ParseListWebhooksResponse(rsp *http.Response) (*ListWebhooksResponse, error) { +// ParseRepositionCardStepResponse parses an HTTP response from a RepositionCardStepWithResponse call +func ParseRepositionCardStepResponse(rsp *http.Response) (*RepositionCardStepResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &ListWebhooksResponse{ + response := &RepositionCardStepResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest ListWebhooksResponseContent - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest + case rsp.StatusCode == 200: + break // No content-type case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: var dest UnauthorizedErrorResponseContent @@ -30417,6 +31794,13 @@ func ParseListWebhooksResponse(rsp *http.Response) (*ListWebhooksResponse, error } response.JSON403 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 422: + var dest ValidationErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON422 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 429: var dest RateLimitErrorResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { @@ -30436,34 +31820,27 @@ func ParseListWebhooksResponse(rsp *http.Response) (*ListWebhooksResponse, error return response, nil } -// ParseCreateWebhookResponse parses an HTTP response from a CreateWebhookWithResponse call -func ParseCreateWebhookResponse(rsp *http.Response) (*CreateWebhookResponse, error) { +// ParseCreateCardStepResponse parses an HTTP response from a CreateCardStepWithResponse call +func ParseCreateCardStepResponse(rsp *http.Response) (*CreateCardStepResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &CreateWebhookResponse{ + response := &CreateCardStepResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: - var dest CreateWebhookResponseContent + var dest CreateCardStepResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } response.JSON201 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: - var dest BadRequestErrorResponseContent - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON400 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: var dest UnauthorizedErrorResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { @@ -30478,6 +31855,13 @@ func ParseCreateWebhookResponse(rsp *http.Response) (*CreateWebhookResponse, err } response.JSON403 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 422: + var dest ValidationErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON422 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 429: var dest RateLimitErrorResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { @@ -30492,34 +31876,27 @@ func ParseCreateWebhookResponse(rsp *http.Response) (*CreateWebhookResponse, err } response.JSON500 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 507: - var dest WebhookLimitErrorResponseContent - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON507 = &dest - } return response, nil } -// ParseGetCardResponse parses an HTTP response from a GetCardWithResponse call -func ParseGetCardResponse(rsp *http.Response) (*GetCardResponse, error) { +// ParseGetCardColumnResponse parses an HTTP response from a GetCardColumnWithResponse call +func ParseGetCardColumnResponse(rsp *http.Response) (*GetCardColumnResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetCardResponse{ + response := &GetCardColumnResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest GetCardResponseContent + var dest GetCardColumnResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -30558,22 +31935,22 @@ func ParseGetCardResponse(rsp *http.Response) (*GetCardResponse, error) { return response, nil } -// ParseUpdateCardResponse parses an HTTP response from a UpdateCardWithResponse call -func ParseUpdateCardResponse(rsp *http.Response) (*UpdateCardResponse, error) { +// ParseUpdateCardColumnResponse parses an HTTP response from a UpdateCardColumnWithResponse call +func ParseUpdateCardColumnResponse(rsp *http.Response) (*UpdateCardColumnResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &UpdateCardResponse{ + response := &UpdateCardColumnResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest UpdateCardResponseContent + var dest UpdateCardColumnResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -30619,22 +31996,26 @@ func ParseUpdateCardResponse(rsp *http.Response) (*UpdateCardResponse, error) { return response, nil } -// ParseMoveCardResponse parses an HTTP response from a MoveCardWithResponse call -func ParseMoveCardResponse(rsp *http.Response) (*MoveCardResponse, error) { +// ParseListCardsResponse parses an HTTP response from a ListCardsWithResponse call +func ParseListCardsResponse(rsp *http.Response) (*ListCardsResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &MoveCardResponse{ + response := &ListCardsResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case rsp.StatusCode == 204: - break // No content-type + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest ListCardsResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: var dest UnauthorizedErrorResponseContent @@ -30650,13 +32031,6 @@ func ParseMoveCardResponse(rsp *http.Response) (*MoveCardResponse, error) { } response.JSON403 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 422: - var dest ValidationErrorResponseContent - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON422 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 429: var dest RateLimitErrorResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { @@ -30676,22 +32050,26 @@ func ParseMoveCardResponse(rsp *http.Response) (*MoveCardResponse, error) { return response, nil } -// ParseRepositionCardStepResponse parses an HTTP response from a RepositionCardStepWithResponse call -func ParseRepositionCardStepResponse(rsp *http.Response) (*RepositionCardStepResponse, error) { +// ParseCreateCardResponse parses an HTTP response from a CreateCardWithResponse call +func ParseCreateCardResponse(rsp *http.Response) (*CreateCardResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &RepositionCardStepResponse{ + response := &CreateCardResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case rsp.StatusCode == 200: - break // No content-type + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest CreateCardResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON201 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: var dest UnauthorizedErrorResponseContent @@ -30733,26 +32111,72 @@ func ParseRepositionCardStepResponse(rsp *http.Response) (*RepositionCardStepRes return response, nil } -// ParseCreateCardStepResponse parses an HTTP response from a CreateCardStepWithResponse call -func ParseCreateCardStepResponse(rsp *http.Response) (*CreateCardStepResponse, error) { +// ParseUnsubscribeFromCardColumnResponse parses an HTTP response from a UnsubscribeFromCardColumnWithResponse call +func ParseUnsubscribeFromCardColumnResponse(rsp *http.Response) (*UnsubscribeFromCardColumnResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &CreateCardStepResponse{ + response := &UnsubscribeFromCardColumnResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: - var dest CreateCardStepResponseContent + case rsp.StatusCode == 204: + break // No content-type + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: + var dest UnauthorizedErrorResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON201 = &dest + response.JSON401 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: + var dest ForbiddenErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON403 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest NotFoundErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest InternalServerErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + +// ParseSubscribeToCardColumnResponse parses an HTTP response from a SubscribeToCardColumnWithResponse call +func ParseSubscribeToCardColumnResponse(rsp *http.Response) (*SubscribeToCardColumnResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &SubscribeToCardColumnResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case rsp.StatusCode == 204: + break // No content-type case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: var dest UnauthorizedErrorResponseContent @@ -30768,12 +32192,12 @@ func ParseCreateCardStepResponse(rsp *http.Response) (*CreateCardStepResponse, e } response.JSON403 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 422: - var dest ValidationErrorResponseContent + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest NotFoundErrorResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON422 = &dest + response.JSON404 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 429: var dest RateLimitErrorResponseContent @@ -30794,22 +32218,76 @@ func ParseCreateCardStepResponse(rsp *http.Response) (*CreateCardStepResponse, e return response, nil } -// ParseGetCardColumnResponse parses an HTTP response from a GetCardColumnWithResponse call -func ParseGetCardColumnResponse(rsp *http.Response) (*GetCardColumnResponse, error) { +// ParseGetCardStepResponse parses an HTTP response from a GetCardStepWithResponse call +func ParseGetCardStepResponse(rsp *http.Response) (*GetCardStepResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetCardColumnResponse{ + response := &GetCardStepResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest GetCardColumnResponseContent + var dest GetCardStepResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: + var dest UnauthorizedErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON401 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: + var dest ForbiddenErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON403 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest NotFoundErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest InternalServerErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + +// ParseUpdateCardStepResponse parses an HTTP response from a UpdateCardStepWithResponse call +func ParseUpdateCardStepResponse(rsp *http.Response) (*UpdateCardStepResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &UpdateCardStepResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest UpdateCardStepResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -30836,6 +32314,13 @@ func ParseGetCardColumnResponse(rsp *http.Response) (*GetCardColumnResponse, err } response.JSON404 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 422: + var dest ValidationErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON422 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: var dest InternalServerErrorResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { @@ -30848,22 +32333,22 @@ func ParseGetCardColumnResponse(rsp *http.Response) (*GetCardColumnResponse, err return response, nil } -// ParseUpdateCardColumnResponse parses an HTTP response from a UpdateCardColumnWithResponse call -func ParseUpdateCardColumnResponse(rsp *http.Response) (*UpdateCardColumnResponse, error) { +// ParseSetCardStepCompletionResponse parses an HTTP response from a SetCardStepCompletionWithResponse call +func ParseSetCardStepCompletionResponse(rsp *http.Response) (*SetCardStepCompletionResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &UpdateCardColumnResponse{ + response := &SetCardStepCompletionResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest UpdateCardColumnResponseContent + var dest SetCardStepCompletionResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -30909,22 +32394,22 @@ func ParseUpdateCardColumnResponse(rsp *http.Response) (*UpdateCardColumnRespons return response, nil } -// ParseListCardsResponse parses an HTTP response from a ListCardsWithResponse call -func ParseListCardsResponse(rsp *http.Response) (*ListCardsResponse, error) { +// ParseGetCardTableResponse parses an HTTP response from a GetCardTableWithResponse call +func ParseGetCardTableResponse(rsp *http.Response) (*GetCardTableResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &ListCardsResponse{ + response := &GetCardTableResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest ListCardsResponseContent + var dest GetCardTableResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -30944,12 +32429,12 @@ func ParseListCardsResponse(rsp *http.Response) (*ListCardsResponse, error) { } response.JSON403 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 429: - var dest RateLimitErrorResponseContent + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest NotFoundErrorResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON429 = &dest + response.JSON404 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: var dest InternalServerErrorResponseContent @@ -30963,22 +32448,22 @@ func ParseListCardsResponse(rsp *http.Response) (*ListCardsResponse, error) { return response, nil } -// ParseCreateCardResponse parses an HTTP response from a CreateCardWithResponse call -func ParseCreateCardResponse(rsp *http.Response) (*CreateCardResponse, error) { +// ParseCreateCardColumnResponse parses an HTTP response from a CreateCardColumnWithResponse call +func ParseCreateCardColumnResponse(rsp *http.Response) (*CreateCardColumnResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &CreateCardResponse{ + response := &CreateCardColumnResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: - var dest CreateCardResponseContent + var dest CreateCardColumnResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -31024,15 +32509,15 @@ func ParseCreateCardResponse(rsp *http.Response) (*CreateCardResponse, error) { return response, nil } -// ParseUnsubscribeFromCardColumnResponse parses an HTTP response from a UnsubscribeFromCardColumnWithResponse call -func ParseUnsubscribeFromCardColumnResponse(rsp *http.Response) (*UnsubscribeFromCardColumnResponse, error) { +// ParseMoveCardColumnResponse parses an HTTP response from a MoveCardColumnWithResponse call +func ParseMoveCardColumnResponse(rsp *http.Response) (*MoveCardColumnResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &UnsubscribeFromCardColumnResponse{ + response := &MoveCardColumnResponse{ Body: bodyBytes, HTTPResponse: rsp, } @@ -31055,12 +32540,19 @@ func ParseUnsubscribeFromCardColumnResponse(rsp *http.Response) (*UnsubscribeFro } response.JSON403 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: - var dest NotFoundErrorResponseContent + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 422: + var dest ValidationErrorResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON404 = &dest + response.JSON422 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 429: + var dest RateLimitErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON429 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: var dest InternalServerErrorResponseContent @@ -31074,22 +32566,26 @@ func ParseUnsubscribeFromCardColumnResponse(rsp *http.Response) (*UnsubscribeFro return response, nil } -// ParseSubscribeToCardColumnResponse parses an HTTP response from a SubscribeToCardColumnWithResponse call -func ParseSubscribeToCardColumnResponse(rsp *http.Response) (*SubscribeToCardColumnResponse, error) { +// ParseGetEverythingOverdueCardsResponse parses an HTTP response from a GetEverythingOverdueCardsWithResponse call +func ParseGetEverythingOverdueCardsResponse(rsp *http.Response) (*GetEverythingOverdueCardsResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &SubscribeToCardColumnResponse{ + response := &GetEverythingOverdueCardsResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case rsp.StatusCode == 204: - break // No content-type + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest GetEverythingOverdueCardsResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: var dest UnauthorizedErrorResponseContent @@ -31105,20 +32601,6 @@ func ParseSubscribeToCardColumnResponse(rsp *http.Response) (*SubscribeToCardCol } response.JSON403 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: - var dest NotFoundErrorResponseContent - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON404 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 429: - var dest RateLimitErrorResponseContent - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON429 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: var dest InternalServerErrorResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { @@ -31131,22 +32613,22 @@ func ParseSubscribeToCardColumnResponse(rsp *http.Response) (*SubscribeToCardCol return response, nil } -// ParseGetCardStepResponse parses an HTTP response from a GetCardStepWithResponse call -func ParseGetCardStepResponse(rsp *http.Response) (*GetCardStepResponse, error) { +// ParseListMessageTypesResponse parses an HTTP response from a ListMessageTypesWithResponse call +func ParseListMessageTypesResponse(rsp *http.Response) (*ListMessageTypesResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetCardStepResponse{ + response := &ListMessageTypesResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest GetCardStepResponseContent + var dest ListMessageTypesResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -31166,12 +32648,12 @@ func ParseGetCardStepResponse(rsp *http.Response) (*GetCardStepResponse, error) } response.JSON403 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: - var dest NotFoundErrorResponseContent + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 429: + var dest RateLimitErrorResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON404 = &dest + response.JSON429 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: var dest InternalServerErrorResponseContent @@ -31185,26 +32667,26 @@ func ParseGetCardStepResponse(rsp *http.Response) (*GetCardStepResponse, error) return response, nil } -// ParseUpdateCardStepResponse parses an HTTP response from a UpdateCardStepWithResponse call -func ParseUpdateCardStepResponse(rsp *http.Response) (*UpdateCardStepResponse, error) { +// ParseCreateMessageTypeResponse parses an HTTP response from a CreateMessageTypeWithResponse call +func ParseCreateMessageTypeResponse(rsp *http.Response) (*CreateMessageTypeResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &UpdateCardStepResponse{ + response := &CreateMessageTypeResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest UpdateCardStepResponseContent + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest CreateMessageTypeResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON200 = &dest + response.JSON201 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: var dest UnauthorizedErrorResponseContent @@ -31220,19 +32702,19 @@ func ParseUpdateCardStepResponse(rsp *http.Response) (*UpdateCardStepResponse, e } response.JSON403 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: - var dest NotFoundErrorResponseContent + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 422: + var dest ValidationErrorResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON404 = &dest + response.JSON422 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 422: - var dest ValidationErrorResponseContent + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 429: + var dest RateLimitErrorResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON422 = &dest + response.JSON429 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: var dest InternalServerErrorResponseContent @@ -31246,26 +32728,22 @@ func ParseUpdateCardStepResponse(rsp *http.Response) (*UpdateCardStepResponse, e return response, nil } -// ParseSetCardStepCompletionResponse parses an HTTP response from a SetCardStepCompletionWithResponse call -func ParseSetCardStepCompletionResponse(rsp *http.Response) (*SetCardStepCompletionResponse, error) { +// ParseDeleteMessageTypeResponse parses an HTTP response from a DeleteMessageTypeWithResponse call +func ParseDeleteMessageTypeResponse(rsp *http.Response) (*DeleteMessageTypeResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &SetCardStepCompletionResponse{ + response := &DeleteMessageTypeResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest SetCardStepCompletionResponseContent - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest + case rsp.StatusCode == 204: + break // No content-type case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: var dest UnauthorizedErrorResponseContent @@ -31288,13 +32766,6 @@ func ParseSetCardStepCompletionResponse(rsp *http.Response) (*SetCardStepComplet } response.JSON404 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 422: - var dest ValidationErrorResponseContent - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON422 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: var dest InternalServerErrorResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { @@ -31307,22 +32778,22 @@ func ParseSetCardStepCompletionResponse(rsp *http.Response) (*SetCardStepComplet return response, nil } -// ParseGetCardTableResponse parses an HTTP response from a GetCardTableWithResponse call -func ParseGetCardTableResponse(rsp *http.Response) (*GetCardTableResponse, error) { +// ParseGetMessageTypeResponse parses an HTTP response from a GetMessageTypeWithResponse call +func ParseGetMessageTypeResponse(rsp *http.Response) (*GetMessageTypeResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetCardTableResponse{ + response := &GetMessageTypeResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest GetCardTableResponseContent + var dest GetMessageTypeResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -31361,26 +32832,26 @@ func ParseGetCardTableResponse(rsp *http.Response) (*GetCardTableResponse, error return response, nil } -// ParseCreateCardColumnResponse parses an HTTP response from a CreateCardColumnWithResponse call -func ParseCreateCardColumnResponse(rsp *http.Response) (*CreateCardColumnResponse, error) { +// ParseUpdateMessageTypeResponse parses an HTTP response from a UpdateMessageTypeWithResponse call +func ParseUpdateMessageTypeResponse(rsp *http.Response) (*UpdateMessageTypeResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &CreateCardColumnResponse{ + response := &UpdateMessageTypeResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: - var dest CreateCardColumnResponseContent + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest UpdateMessageTypeResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON201 = &dest + response.JSON200 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: var dest UnauthorizedErrorResponseContent @@ -31396,19 +32867,19 @@ func ParseCreateCardColumnResponse(rsp *http.Response) (*CreateCardColumnRespons } response.JSON403 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 422: - var dest ValidationErrorResponseContent + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest NotFoundErrorResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON422 = &dest + response.JSON404 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 429: - var dest RateLimitErrorResponseContent + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 422: + var dest ValidationErrorResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON429 = &dest + response.JSON422 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: var dest InternalServerErrorResponseContent @@ -31422,22 +32893,26 @@ func ParseCreateCardColumnResponse(rsp *http.Response) (*CreateCardColumnRespons return response, nil } -// ParseMoveCardColumnResponse parses an HTTP response from a MoveCardColumnWithResponse call -func ParseMoveCardColumnResponse(rsp *http.Response) (*MoveCardColumnResponse, error) { +// ParseListCampfiresResponse parses an HTTP response from a ListCampfiresWithResponse call +func ParseListCampfiresResponse(rsp *http.Response) (*ListCampfiresResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &MoveCardColumnResponse{ + response := &ListCampfiresResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case rsp.StatusCode == 204: - break // No content-type + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest ListCampfiresResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: var dest UnauthorizedErrorResponseContent @@ -31453,13 +32928,6 @@ func ParseMoveCardColumnResponse(rsp *http.Response) (*MoveCardColumnResponse, e } response.JSON403 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 422: - var dest ValidationErrorResponseContent - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON422 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 429: var dest RateLimitErrorResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { @@ -31479,22 +32947,22 @@ func ParseMoveCardColumnResponse(rsp *http.Response) (*MoveCardColumnResponse, e return response, nil } -// ParseListMessageTypesResponse parses an HTTP response from a ListMessageTypesWithResponse call -func ParseListMessageTypesResponse(rsp *http.Response) (*ListMessageTypesResponse, error) { +// ParseGetCampfireResponse parses an HTTP response from a GetCampfireWithResponse call +func ParseGetCampfireResponse(rsp *http.Response) (*GetCampfireResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &ListMessageTypesResponse{ + response := &GetCampfireResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest ListMessageTypesResponseContent + var dest GetCampfireResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -31514,12 +32982,12 @@ func ParseListMessageTypesResponse(rsp *http.Response) (*ListMessageTypesRespons } response.JSON403 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 429: - var dest RateLimitErrorResponseContent + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest NotFoundErrorResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON429 = &dest + response.JSON404 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: var dest InternalServerErrorResponseContent @@ -31533,26 +33001,26 @@ func ParseListMessageTypesResponse(rsp *http.Response) (*ListMessageTypesRespons return response, nil } -// ParseCreateMessageTypeResponse parses an HTTP response from a CreateMessageTypeWithResponse call -func ParseCreateMessageTypeResponse(rsp *http.Response) (*CreateMessageTypeResponse, error) { +// ParseListChatbotsResponse parses an HTTP response from a ListChatbotsWithResponse call +func ParseListChatbotsResponse(rsp *http.Response) (*ListChatbotsResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &CreateMessageTypeResponse{ + response := &ListChatbotsResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: - var dest CreateMessageTypeResponseContent + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest ListChatbotsResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON201 = &dest + response.JSON200 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: var dest UnauthorizedErrorResponseContent @@ -31568,13 +33036,6 @@ func ParseCreateMessageTypeResponse(rsp *http.Response) (*CreateMessageTypeRespo } response.JSON403 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 422: - var dest ValidationErrorResponseContent - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON422 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 429: var dest RateLimitErrorResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { @@ -31594,22 +33055,26 @@ func ParseCreateMessageTypeResponse(rsp *http.Response) (*CreateMessageTypeRespo return response, nil } -// ParseDeleteMessageTypeResponse parses an HTTP response from a DeleteMessageTypeWithResponse call -func ParseDeleteMessageTypeResponse(rsp *http.Response) (*DeleteMessageTypeResponse, error) { +// ParseCreateChatbotResponse parses an HTTP response from a CreateChatbotWithResponse call +func ParseCreateChatbotResponse(rsp *http.Response) (*CreateChatbotResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &DeleteMessageTypeResponse{ + response := &CreateChatbotResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case rsp.StatusCode == 204: - break // No content-type + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest CreateChatbotResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON201 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: var dest UnauthorizedErrorResponseContent @@ -31625,12 +33090,19 @@ func ParseDeleteMessageTypeResponse(rsp *http.Response) (*DeleteMessageTypeRespo } response.JSON403 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: - var dest NotFoundErrorResponseContent + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 422: + var dest ValidationErrorResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON404 = &dest + response.JSON422 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 429: + var dest RateLimitErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON429 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: var dest InternalServerErrorResponseContent @@ -31644,26 +33116,22 @@ func ParseDeleteMessageTypeResponse(rsp *http.Response) (*DeleteMessageTypeRespo return response, nil } -// ParseGetMessageTypeResponse parses an HTTP response from a GetMessageTypeWithResponse call -func ParseGetMessageTypeResponse(rsp *http.Response) (*GetMessageTypeResponse, error) { +// ParseDeleteChatbotResponse parses an HTTP response from a DeleteChatbotWithResponse call +func ParseDeleteChatbotResponse(rsp *http.Response) (*DeleteChatbotResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetMessageTypeResponse{ + response := &DeleteChatbotResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest GetMessageTypeResponseContent - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest + case rsp.StatusCode == 204: + break // No content-type case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: var dest UnauthorizedErrorResponseContent @@ -31698,22 +33166,22 @@ func ParseGetMessageTypeResponse(rsp *http.Response) (*GetMessageTypeResponse, e return response, nil } -// ParseUpdateMessageTypeResponse parses an HTTP response from a UpdateMessageTypeWithResponse call -func ParseUpdateMessageTypeResponse(rsp *http.Response) (*UpdateMessageTypeResponse, error) { +// ParseGetChatbotResponse parses an HTTP response from a GetChatbotWithResponse call +func ParseGetChatbotResponse(rsp *http.Response) (*GetChatbotResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &UpdateMessageTypeResponse{ + response := &GetChatbotResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest UpdateMessageTypeResponseContent + var dest GetChatbotResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -31740,13 +33208,6 @@ func ParseUpdateMessageTypeResponse(rsp *http.Response) (*UpdateMessageTypeRespo } response.JSON404 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 422: - var dest ValidationErrorResponseContent - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON422 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: var dest InternalServerErrorResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { @@ -31759,22 +33220,22 @@ func ParseUpdateMessageTypeResponse(rsp *http.Response) (*UpdateMessageTypeRespo return response, nil } -// ParseListCampfiresResponse parses an HTTP response from a ListCampfiresWithResponse call -func ParseListCampfiresResponse(rsp *http.Response) (*ListCampfiresResponse, error) { +// ParseUpdateChatbotResponse parses an HTTP response from a UpdateChatbotWithResponse call +func ParseUpdateChatbotResponse(rsp *http.Response) (*UpdateChatbotResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &ListCampfiresResponse{ + response := &UpdateChatbotResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest ListCampfiresResponseContent + var dest UpdateChatbotResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -31794,66 +33255,19 @@ func ParseListCampfiresResponse(rsp *http.Response) (*ListCampfiresResponse, err } response.JSON403 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 429: - var dest RateLimitErrorResponseContent - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON429 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: - var dest InternalServerErrorResponseContent - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON500 = &dest - - } - - return response, nil -} - -// ParseGetCampfireResponse parses an HTTP response from a GetCampfireWithResponse call -func ParseGetCampfireResponse(rsp *http.Response) (*GetCampfireResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &GetCampfireResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest GetCampfireResponseContent - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: - var dest UnauthorizedErrorResponseContent - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON401 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: - var dest ForbiddenErrorResponseContent + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest NotFoundErrorResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON403 = &dest + response.JSON404 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: - var dest NotFoundErrorResponseContent + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 422: + var dest ValidationErrorResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON404 = &dest + response.JSON422 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: var dest InternalServerErrorResponseContent @@ -31867,22 +33281,22 @@ func ParseGetCampfireResponse(rsp *http.Response) (*GetCampfireResponse, error) return response, nil } -// ParseListChatbotsResponse parses an HTTP response from a ListChatbotsWithResponse call -func ParseListChatbotsResponse(rsp *http.Response) (*ListChatbotsResponse, error) { +// ParseListCampfireLinesResponse parses an HTTP response from a ListCampfireLinesWithResponse call +func ParseListCampfireLinesResponse(rsp *http.Response) (*ListCampfireLinesResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &ListChatbotsResponse{ + response := &ListCampfireLinesResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest ListChatbotsResponseContent + var dest ListCampfireLinesResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -31921,22 +33335,22 @@ func ParseListChatbotsResponse(rsp *http.Response) (*ListChatbotsResponse, error return response, nil } -// ParseCreateChatbotResponse parses an HTTP response from a CreateChatbotWithResponse call -func ParseCreateChatbotResponse(rsp *http.Response) (*CreateChatbotResponse, error) { +// ParseCreateCampfireLineResponse parses an HTTP response from a CreateCampfireLineWithResponse call +func ParseCreateCampfireLineResponse(rsp *http.Response) (*CreateCampfireLineResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &CreateChatbotResponse{ + response := &CreateCampfireLineResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: - var dest CreateChatbotResponseContent + var dest CreateCampfireLineResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -31982,15 +33396,15 @@ func ParseCreateChatbotResponse(rsp *http.Response) (*CreateChatbotResponse, err return response, nil } -// ParseDeleteChatbotResponse parses an HTTP response from a DeleteChatbotWithResponse call -func ParseDeleteChatbotResponse(rsp *http.Response) (*DeleteChatbotResponse, error) { +// ParseDeleteCampfireLineResponse parses an HTTP response from a DeleteCampfireLineWithResponse call +func ParseDeleteCampfireLineResponse(rsp *http.Response) (*DeleteCampfireLineResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &DeleteChatbotResponse{ + response := &DeleteCampfireLineResponse{ Body: bodyBytes, HTTPResponse: rsp, } @@ -32032,22 +33446,22 @@ func ParseDeleteChatbotResponse(rsp *http.Response) (*DeleteChatbotResponse, err return response, nil } -// ParseGetChatbotResponse parses an HTTP response from a GetChatbotWithResponse call -func ParseGetChatbotResponse(rsp *http.Response) (*GetChatbotResponse, error) { +// ParseGetCampfireLineResponse parses an HTTP response from a GetCampfireLineWithResponse call +func ParseGetCampfireLineResponse(rsp *http.Response) (*GetCampfireLineResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetChatbotResponse{ + response := &GetCampfireLineResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest GetChatbotResponseContent + var dest GetCampfireLineResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -32086,26 +33500,22 @@ func ParseGetChatbotResponse(rsp *http.Response) (*GetChatbotResponse, error) { return response, nil } -// ParseUpdateChatbotResponse parses an HTTP response from a UpdateChatbotWithResponse call -func ParseUpdateChatbotResponse(rsp *http.Response) (*UpdateChatbotResponse, error) { +// ParseUpdateCampfireLineResponse parses an HTTP response from a UpdateCampfireLineWithResponse call +func ParseUpdateCampfireLineResponse(rsp *http.Response) (*UpdateCampfireLineResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &UpdateChatbotResponse{ + response := &UpdateCampfireLineResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest UpdateChatbotResponseContent - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest + case rsp.StatusCode == 204: + break // No content-type case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: var dest UnauthorizedErrorResponseContent @@ -32135,6 +33545,13 @@ func ParseUpdateChatbotResponse(rsp *http.Response) (*UpdateChatbotResponse, err } response.JSON422 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 429: + var dest RateLimitErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON429 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: var dest InternalServerErrorResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { @@ -32147,22 +33564,22 @@ func ParseUpdateChatbotResponse(rsp *http.Response) (*UpdateChatbotResponse, err return response, nil } -// ParseListCampfireLinesResponse parses an HTTP response from a ListCampfireLinesWithResponse call -func ParseListCampfireLinesResponse(rsp *http.Response) (*ListCampfireLinesResponse, error) { +// ParseListCampfireUploadsResponse parses an HTTP response from a ListCampfireUploadsWithResponse call +func ParseListCampfireUploadsResponse(rsp *http.Response) (*ListCampfireUploadsResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &ListCampfireLinesResponse{ + response := &ListCampfireUploadsResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest ListCampfireLinesResponseContent + var dest ListCampfireUploadsResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -32201,22 +33618,22 @@ func ParseListCampfireLinesResponse(rsp *http.Response) (*ListCampfireLinesRespo return response, nil } -// ParseCreateCampfireLineResponse parses an HTTP response from a CreateCampfireLineWithResponse call -func ParseCreateCampfireLineResponse(rsp *http.Response) (*CreateCampfireLineResponse, error) { +// ParseCreateCampfireUploadResponse parses an HTTP response from a CreateCampfireUploadWithResponse call +func ParseCreateCampfireUploadResponse(rsp *http.Response) (*CreateCampfireUploadResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &CreateCampfireLineResponse{ + response := &CreateCampfireUploadResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: - var dest CreateCampfireLineResponseContent + var dest CreateCampfireUploadResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -32262,22 +33679,26 @@ func ParseCreateCampfireLineResponse(rsp *http.Response) (*CreateCampfireLineRes return response, nil } -// ParseDeleteCampfireLineResponse parses an HTTP response from a DeleteCampfireLineWithResponse call -func ParseDeleteCampfireLineResponse(rsp *http.Response) (*DeleteCampfireLineResponse, error) { +// ParseGetEverythingCheckinsResponse parses an HTTP response from a GetEverythingCheckinsWithResponse call +func ParseGetEverythingCheckinsResponse(rsp *http.Response) (*GetEverythingCheckinsResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &DeleteCampfireLineResponse{ + response := &GetEverythingCheckinsResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case rsp.StatusCode == 204: - break // No content-type + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest GetEverythingCheckinsResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: var dest UnauthorizedErrorResponseContent @@ -32293,12 +33714,12 @@ func ParseDeleteCampfireLineResponse(rsp *http.Response) (*DeleteCampfireLineRes } response.JSON403 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: - var dest NotFoundErrorResponseContent + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 429: + var dest RateLimitErrorResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON404 = &dest + response.JSON429 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: var dest InternalServerErrorResponseContent @@ -32312,22 +33733,22 @@ func ParseDeleteCampfireLineResponse(rsp *http.Response) (*DeleteCampfireLineRes return response, nil } -// ParseGetCampfireLineResponse parses an HTTP response from a GetCampfireLineWithResponse call -func ParseGetCampfireLineResponse(rsp *http.Response) (*GetCampfireLineResponse, error) { +// ParseListPingablePeopleResponse parses an HTTP response from a ListPingablePeopleWithResponse call +func ParseListPingablePeopleResponse(rsp *http.Response) (*ListPingablePeopleResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetCampfireLineResponse{ + response := &ListPingablePeopleResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest GetCampfireLineResponseContent + var dest ListPingablePeopleResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -32347,12 +33768,12 @@ func ParseGetCampfireLineResponse(rsp *http.Response) (*GetCampfireLineResponse, } response.JSON403 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: - var dest NotFoundErrorResponseContent + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 429: + var dest RateLimitErrorResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON404 = &dest + response.JSON429 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: var dest InternalServerErrorResponseContent @@ -32366,22 +33787,26 @@ func ParseGetCampfireLineResponse(rsp *http.Response) (*GetCampfireLineResponse, return response, nil } -// ParseUpdateCampfireLineResponse parses an HTTP response from a UpdateCampfireLineWithResponse call -func ParseUpdateCampfireLineResponse(rsp *http.Response) (*UpdateCampfireLineResponse, error) { +// ParseListClientApprovalsResponse parses an HTTP response from a ListClientApprovalsWithResponse call +func ParseListClientApprovalsResponse(rsp *http.Response) (*ListClientApprovalsResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &UpdateCampfireLineResponse{ + response := &ListClientApprovalsResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case rsp.StatusCode == 204: - break // No content-type + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest ListClientApprovalsResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: var dest UnauthorizedErrorResponseContent @@ -32397,20 +33822,6 @@ func ParseUpdateCampfireLineResponse(rsp *http.Response) (*UpdateCampfireLineRes } response.JSON403 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: - var dest NotFoundErrorResponseContent - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON404 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 422: - var dest ValidationErrorResponseContent - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON422 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 429: var dest RateLimitErrorResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { @@ -32430,22 +33841,22 @@ func ParseUpdateCampfireLineResponse(rsp *http.Response) (*UpdateCampfireLineRes return response, nil } -// ParseListCampfireUploadsResponse parses an HTTP response from a ListCampfireUploadsWithResponse call -func ParseListCampfireUploadsResponse(rsp *http.Response) (*ListCampfireUploadsResponse, error) { +// ParseGetClientApprovalResponse parses an HTTP response from a GetClientApprovalWithResponse call +func ParseGetClientApprovalResponse(rsp *http.Response) (*GetClientApprovalResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &ListCampfireUploadsResponse{ + response := &GetClientApprovalResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest ListCampfireUploadsResponseContent + var dest GetClientApprovalResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -32465,12 +33876,12 @@ func ParseListCampfireUploadsResponse(rsp *http.Response) (*ListCampfireUploadsR } response.JSON403 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 429: - var dest RateLimitErrorResponseContent + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest NotFoundErrorResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON429 = &dest + response.JSON404 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: var dest InternalServerErrorResponseContent @@ -32484,26 +33895,26 @@ func ParseListCampfireUploadsResponse(rsp *http.Response) (*ListCampfireUploadsR return response, nil } -// ParseCreateCampfireUploadResponse parses an HTTP response from a CreateCampfireUploadWithResponse call -func ParseCreateCampfireUploadResponse(rsp *http.Response) (*CreateCampfireUploadResponse, error) { +// ParseListClientCorrespondencesResponse parses an HTTP response from a ListClientCorrespondencesWithResponse call +func ParseListClientCorrespondencesResponse(rsp *http.Response) (*ListClientCorrespondencesResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &CreateCampfireUploadResponse{ + response := &ListClientCorrespondencesResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: - var dest CreateCampfireUploadResponseContent + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest ListClientCorrespondencesResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON201 = &dest + response.JSON200 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: var dest UnauthorizedErrorResponseContent @@ -32519,13 +33930,6 @@ func ParseCreateCampfireUploadResponse(rsp *http.Response) (*CreateCampfireUploa } response.JSON403 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 422: - var dest ValidationErrorResponseContent - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON422 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 429: var dest RateLimitErrorResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { @@ -32545,22 +33949,22 @@ func ParseCreateCampfireUploadResponse(rsp *http.Response) (*CreateCampfireUploa return response, nil } -// ParseListPingablePeopleResponse parses an HTTP response from a ListPingablePeopleWithResponse call -func ParseListPingablePeopleResponse(rsp *http.Response) (*ListPingablePeopleResponse, error) { +// ParseGetClientCorrespondenceResponse parses an HTTP response from a GetClientCorrespondenceWithResponse call +func ParseGetClientCorrespondenceResponse(rsp *http.Response) (*GetClientCorrespondenceResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &ListPingablePeopleResponse{ + response := &GetClientCorrespondenceResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest ListPingablePeopleResponseContent + var dest GetClientCorrespondenceResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -32580,12 +33984,12 @@ func ParseListPingablePeopleResponse(rsp *http.Response) (*ListPingablePeopleRes } response.JSON403 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 429: - var dest RateLimitErrorResponseContent + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest NotFoundErrorResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON429 = &dest + response.JSON404 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: var dest InternalServerErrorResponseContent @@ -32599,22 +34003,22 @@ func ParseListPingablePeopleResponse(rsp *http.Response) (*ListPingablePeopleRes return response, nil } -// ParseListClientApprovalsResponse parses an HTTP response from a ListClientApprovalsWithResponse call -func ParseListClientApprovalsResponse(rsp *http.Response) (*ListClientApprovalsResponse, error) { +// ParseListClientRepliesResponse parses an HTTP response from a ListClientRepliesWithResponse call +func ParseListClientRepliesResponse(rsp *http.Response) (*ListClientRepliesResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &ListClientApprovalsResponse{ + response := &ListClientRepliesResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest ListClientApprovalsResponseContent + var dest ListClientRepliesResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -32653,22 +34057,22 @@ func ParseListClientApprovalsResponse(rsp *http.Response) (*ListClientApprovalsR return response, nil } -// ParseGetClientApprovalResponse parses an HTTP response from a GetClientApprovalWithResponse call -func ParseGetClientApprovalResponse(rsp *http.Response) (*GetClientApprovalResponse, error) { +// ParseGetClientReplyResponse parses an HTTP response from a GetClientReplyWithResponse call +func ParseGetClientReplyResponse(rsp *http.Response) (*GetClientReplyResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetClientApprovalResponse{ + response := &GetClientReplyResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest GetClientApprovalResponseContent + var dest GetClientReplyResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -32707,22 +34111,22 @@ func ParseGetClientApprovalResponse(rsp *http.Response) (*GetClientApprovalRespo return response, nil } -// ParseListClientCorrespondencesResponse parses an HTTP response from a ListClientCorrespondencesWithResponse call -func ParseListClientCorrespondencesResponse(rsp *http.Response) (*ListClientCorrespondencesResponse, error) { +// ParseGetEverythingCommentsResponse parses an HTTP response from a GetEverythingCommentsWithResponse call +func ParseGetEverythingCommentsResponse(rsp *http.Response) (*GetEverythingCommentsResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &ListClientCorrespondencesResponse{ + response := &GetEverythingCommentsResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest ListClientCorrespondencesResponseContent + var dest GetEverythingCommentsResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -32761,22 +34165,22 @@ func ParseListClientCorrespondencesResponse(rsp *http.Response) (*ListClientCorr return response, nil } -// ParseGetClientCorrespondenceResponse parses an HTTP response from a GetClientCorrespondenceWithResponse call -func ParseGetClientCorrespondenceResponse(rsp *http.Response) (*GetClientCorrespondenceResponse, error) { +// ParseGetCommentResponse parses an HTTP response from a GetCommentWithResponse call +func ParseGetCommentResponse(rsp *http.Response) (*GetCommentResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetClientCorrespondenceResponse{ + response := &GetCommentResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest GetClientCorrespondenceResponseContent + var dest GetCommentResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -32815,22 +34219,22 @@ func ParseGetClientCorrespondenceResponse(rsp *http.Response) (*GetClientCorresp return response, nil } -// ParseListClientRepliesResponse parses an HTTP response from a ListClientRepliesWithResponse call -func ParseListClientRepliesResponse(rsp *http.Response) (*ListClientRepliesResponse, error) { +// ParseUpdateCommentResponse parses an HTTP response from a UpdateCommentWithResponse call +func ParseUpdateCommentResponse(rsp *http.Response) (*UpdateCommentResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &ListClientRepliesResponse{ + response := &UpdateCommentResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest ListClientRepliesResponseContent + var dest UpdateCommentResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -32850,12 +34254,19 @@ func ParseListClientRepliesResponse(rsp *http.Response) (*ListClientRepliesRespo } response.JSON403 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 429: - var dest RateLimitErrorResponseContent + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest NotFoundErrorResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON429 = &dest + response.JSON404 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 422: + var dest ValidationErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON422 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: var dest InternalServerErrorResponseContent @@ -32869,26 +34280,22 @@ func ParseListClientRepliesResponse(rsp *http.Response) (*ListClientRepliesRespo return response, nil } -// ParseGetClientReplyResponse parses an HTTP response from a GetClientReplyWithResponse call -func ParseGetClientReplyResponse(rsp *http.Response) (*GetClientReplyResponse, error) { +// ParseDeleteToolResponse parses an HTTP response from a DeleteToolWithResponse call +func ParseDeleteToolResponse(rsp *http.Response) (*DeleteToolResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetClientReplyResponse{ + response := &DeleteToolResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest GetClientReplyResponseContent - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest + case rsp.StatusCode == 204: + break // No content-type case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: var dest UnauthorizedErrorResponseContent @@ -32923,22 +34330,22 @@ func ParseGetClientReplyResponse(rsp *http.Response) (*GetClientReplyResponse, e return response, nil } -// ParseGetCommentResponse parses an HTTP response from a GetCommentWithResponse call -func ParseGetCommentResponse(rsp *http.Response) (*GetCommentResponse, error) { +// ParseGetToolResponse parses an HTTP response from a GetToolWithResponse call +func ParseGetToolResponse(rsp *http.Response) (*GetToolResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetCommentResponse{ + response := &GetToolResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest GetCommentResponseContent + var dest GetToolResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -32977,22 +34384,22 @@ func ParseGetCommentResponse(rsp *http.Response) (*GetCommentResponse, error) { return response, nil } -// ParseUpdateCommentResponse parses an HTTP response from a UpdateCommentWithResponse call -func ParseUpdateCommentResponse(rsp *http.Response) (*UpdateCommentResponse, error) { +// ParseUpdateToolResponse parses an HTTP response from a UpdateToolWithResponse call +func ParseUpdateToolResponse(rsp *http.Response) (*UpdateToolResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &UpdateCommentResponse{ + response := &UpdateToolResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest UpdateCommentResponseContent + var dest UpdateToolResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -33038,72 +34445,22 @@ func ParseUpdateCommentResponse(rsp *http.Response) (*UpdateCommentResponse, err return response, nil } -// ParseDeleteToolResponse parses an HTTP response from a DeleteToolWithResponse call -func ParseDeleteToolResponse(rsp *http.Response) (*DeleteToolResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &DeleteToolResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case rsp.StatusCode == 204: - break // No content-type - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: - var dest UnauthorizedErrorResponseContent - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON401 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: - var dest ForbiddenErrorResponseContent - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON403 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: - var dest NotFoundErrorResponseContent - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON404 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: - var dest InternalServerErrorResponseContent - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON500 = &dest - - } - - return response, nil -} - -// ParseGetToolResponse parses an HTTP response from a GetToolWithResponse call -func ParseGetToolResponse(rsp *http.Response) (*GetToolResponse, error) { +// ParseGetDocumentResponse parses an HTTP response from a GetDocumentWithResponse call +func ParseGetDocumentResponse(rsp *http.Response) (*GetDocumentResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetToolResponse{ + response := &GetDocumentResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest GetToolResponseContent + var dest GetDocumentResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -33142,22 +34499,22 @@ func ParseGetToolResponse(rsp *http.Response) (*GetToolResponse, error) { return response, nil } -// ParseUpdateToolResponse parses an HTTP response from a UpdateToolWithResponse call -func ParseUpdateToolResponse(rsp *http.Response) (*UpdateToolResponse, error) { +// ParseUpdateDocumentResponse parses an HTTP response from a UpdateDocumentWithResponse call +func ParseUpdateDocumentResponse(rsp *http.Response) (*UpdateDocumentResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &UpdateToolResponse{ + response := &UpdateDocumentResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest UpdateToolResponseContent + var dest UpdateDocumentResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -33203,22 +34560,22 @@ func ParseUpdateToolResponse(rsp *http.Response) (*UpdateToolResponse, error) { return response, nil } -// ParseGetDocumentResponse parses an HTTP response from a GetDocumentWithResponse call -func ParseGetDocumentResponse(rsp *http.Response) (*GetDocumentResponse, error) { +// ParseGetEverythingFilesResponse parses an HTTP response from a GetEverythingFilesWithResponse call +func ParseGetEverythingFilesResponse(rsp *http.Response) (*GetEverythingFilesResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetDocumentResponse{ + response := &GetEverythingFilesResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest GetDocumentResponseContent + var dest GetEverythingFilesResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -33238,12 +34595,12 @@ func ParseGetDocumentResponse(rsp *http.Response) (*GetDocumentResponse, error) } response.JSON403 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: - var dest NotFoundErrorResponseContent + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 429: + var dest RateLimitErrorResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON404 = &dest + response.JSON429 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: var dest InternalServerErrorResponseContent @@ -33257,22 +34614,22 @@ func ParseGetDocumentResponse(rsp *http.Response) (*GetDocumentResponse, error) return response, nil } -// ParseUpdateDocumentResponse parses an HTTP response from a UpdateDocumentWithResponse call -func ParseUpdateDocumentResponse(rsp *http.Response) (*UpdateDocumentResponse, error) { +// ParseGetEverythingForwardsResponse parses an HTTP response from a GetEverythingForwardsWithResponse call +func ParseGetEverythingForwardsResponse(rsp *http.Response) (*GetEverythingForwardsResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &UpdateDocumentResponse{ + response := &GetEverythingForwardsResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest UpdateDocumentResponseContent + var dest GetEverythingForwardsResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -33292,19 +34649,12 @@ func ParseUpdateDocumentResponse(rsp *http.Response) (*UpdateDocumentResponse, e } response.JSON403 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: - var dest NotFoundErrorResponseContent - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON404 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 422: - var dest ValidationErrorResponseContent + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 429: + var dest RateLimitErrorResponseContent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON422 = &dest + response.JSON429 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: var dest InternalServerErrorResponseContent @@ -34215,6 +35565,60 @@ func ParseCreateMessageResponse(rsp *http.Response) (*CreateMessageResponse, err return response, nil } +// ParseGetEverythingMessagesResponse parses an HTTP response from a GetEverythingMessagesWithResponse call +func ParseGetEverythingMessagesResponse(rsp *http.Response) (*GetEverythingMessagesResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetEverythingMessagesResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest GetEverythingMessagesResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: + var dest UnauthorizedErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON401 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: + var dest ForbiddenErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON403 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 429: + var dest RateLimitErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON429 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest InternalServerErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + // ParseGetMessageResponse parses an HTTP response from a GetMessageWithResponse call func ParseGetMessageResponse(rsp *http.Response) (*GetMessageResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -39881,6 +41285,53 @@ func ParseCreateTodoResponse(rsp *http.Response) (*CreateTodoResponse, error) { return response, nil } +// ParseGetEverythingOverdueTodosResponse parses an HTTP response from a GetEverythingOverdueTodosWithResponse call +func ParseGetEverythingOverdueTodosResponse(rsp *http.Response) (*GetEverythingOverdueTodosResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetEverythingOverdueTodosResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest GetEverythingOverdueTodosResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: + var dest UnauthorizedErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON401 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: + var dest ForbiddenErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON403 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest InternalServerErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + // ParseTrashTodoResponse parses an HTTP response from a TrashTodoWithResponse call func ParseTrashTodoResponse(rsp *http.Response) (*TrashTodoResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) diff --git a/kotlin/conformance/src/main/kotlin/com/basecamp/sdk/conformance/Main.kt b/kotlin/conformance/src/main/kotlin/com/basecamp/sdk/conformance/Main.kt index b28b461fb..06d1c8d3b 100644 --- a/kotlin/conformance/src/main/kotlin/com/basecamp/sdk/conformance/Main.kt +++ b/kotlin/conformance/src/main/kotlin/com/basecamp/sdk/conformance/Main.kt @@ -791,6 +791,46 @@ private suspend fun dispatchOperation(tc: TestCase, account: AccountClient): Dis DispatchResult() } + "GetEverythingMessages" -> { + account.everything.everythingMessages() + DispatchResult() + } + + "GetEverythingComments" -> { + account.everything.everythingComments() + DispatchResult() + } + + "GetEverythingCheckins" -> { + account.everything.everythingCheckins() + DispatchResult() + } + + "GetEverythingForwards" -> { + account.everything.everythingForwards() + DispatchResult() + } + + "GetEverythingBoosts" -> { + account.everything.everythingBoosts() + DispatchResult() + } + + "GetEverythingFiles" -> { + account.everything.everythingFiles() + DispatchResult() + } + + "GetEverythingOverdueTodos" -> { + account.everything.everythingOverdueTodos() + DispatchResult() + } + + "GetEverythingOverdueCards" -> { + account.everything.everythingOverdueCards() + DispatchResult() + } + else -> throw UnsupportedOperationException("Unknown operation: ${tc.operation}") } diff --git a/kotlin/generator/src/main/kotlin/com/basecamp/sdk/generator/Config.kt b/kotlin/generator/src/main/kotlin/com/basecamp/sdk/generator/Config.kt index 8892a0b42..8d53b1565 100644 --- a/kotlin/generator/src/main/kotlin/com/basecamp/sdk/generator/Config.kt +++ b/kotlin/generator/src/main/kotlin/com/basecamp/sdk/generator/Config.kt @@ -314,6 +314,7 @@ val TYPE_ALIASES = mapOf( "ClientReply" to "ClientReply", "Boost" to "Boost", "Notification" to "Notification", + "EverythingFile" to "EverythingFile", "TimelineEvent" to "TimelineEvent", "TimesheetEntry" to "TimesheetEntry", "HillChart" to "HillChart", diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/Metadata.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/Metadata.kt index 896be78a0..b01479ccb 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/Metadata.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/Metadata.kt @@ -87,6 +87,14 @@ object Metadata { "GetClientReply" to OperationConfig(false, RetryConfig(3, 1000L, "exponential", setOf(429, 503))), "GetComment" to OperationConfig(false, RetryConfig(3, 1000L, "exponential", setOf(429, 503))), "GetDocument" to OperationConfig(false, RetryConfig(3, 1000L, "exponential", setOf(429, 503))), + "GetEverythingBoosts" to OperationConfig(false, RetryConfig(3, 1000L, "exponential", setOf(429, 503))), + "GetEverythingCheckins" to OperationConfig(false, RetryConfig(3, 1000L, "exponential", setOf(429, 503))), + "GetEverythingComments" to OperationConfig(false, RetryConfig(3, 1000L, "exponential", setOf(429, 503))), + "GetEverythingFiles" to OperationConfig(false, RetryConfig(3, 1000L, "exponential", setOf(429, 503))), + "GetEverythingForwards" to OperationConfig(false, RetryConfig(3, 1000L, "exponential", setOf(429, 503))), + "GetEverythingMessages" to OperationConfig(false, RetryConfig(3, 1000L, "exponential", setOf(429, 503))), + "GetEverythingOverdueCards" to OperationConfig(false, RetryConfig(3, 1000L, "exponential", setOf(429, 503))), + "GetEverythingOverdueTodos" to OperationConfig(false, RetryConfig(3, 1000L, "exponential", setOf(429, 503))), "GetForward" to OperationConfig(false, RetryConfig(3, 1000L, "exponential", setOf(429, 503))), "GetForwardReply" to OperationConfig(false, RetryConfig(3, 1000L, "exponential", setOf(429, 503))), "GetGaugeNeedle" to OperationConfig(false, RetryConfig(3, 1000L, "exponential", setOf(429, 503))), diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/ServiceAccessors.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/ServiceAccessors.kt index 0085111b0..53af221f1 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/ServiceAccessors.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/ServiceAccessors.kt @@ -79,6 +79,10 @@ val AccountClient.documents: DocumentsService val AccountClient.events: EventsService get() = service("Events") { EventsService(this) } +/** Everything operations. */ +val AccountClient.everything: EverythingService + get() = service("Everything") { EverythingService(this) } + /** Forwards operations. */ val AccountClient.forwards: ForwardsService get() = service("Forwards") { ForwardsService(this) } diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/EverythingFile.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/EverythingFile.kt new file mode 100644 index 000000000..c0459879e --- /dev/null +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/EverythingFile.kt @@ -0,0 +1,48 @@ +package com.basecamp.sdk.generated.models + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonObject +import com.basecamp.sdk.serialization.FlexibleIntSerializer + +/** + * EverythingFile entity from the Basecamp API. + * + * @generated from OpenAPI spec — do not edit directly + */ +@Serializable +data class EverythingFile( + val id: Long = 0L, + val status: String? = null, + @SerialName("visible_to_clients") val visibleToClients: Boolean = false, + @SerialName("created_at") val createdAt: String? = null, + @SerialName("updated_at") val updatedAt: String? = null, + val title: String? = null, + @SerialName("inherits_status") val inheritsStatus: Boolean = false, + val type: String? = null, + val url: String? = null, + @SerialName("app_url") val appUrl: String? = null, + @SerialName("bookmark_url") val bookmarkUrl: String? = null, + @SerialName("subscription_url") val subscriptionUrl: String? = null, + @SerialName("comments_count") val commentsCount: Int = 0, + @SerialName("comments_url") val commentsUrl: String? = null, + @SerialName("boosts_count") val boostsCount: Int = 0, + @SerialName("boosts_url") val boostsUrl: String? = null, + val position: Int = 0, + val parent: RecordingParent? = null, + val bucket: RecordingBucket? = null, + val creator: Person? = null, + @SerialName("attachable_sgid") val attachableSgid: String? = null, + @SerialName("content_type") val contentType: String? = null, + @SerialName("byte_size") val byteSize: Long = 0L, + val filename: String? = null, + @SerialName("download_url") val downloadUrl: String? = null, + @SerialName("app_download_url") val appDownloadUrl: String? = null, + @Serializable(with = FlexibleIntSerializer::class) + val width: Int? = null, + @Serializable(with = FlexibleIntSerializer::class) + val height: Int? = null, + val description: String? = null, + @SerialName("description_attachments") val descriptionAttachments: List? = null +) diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/services/Types.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/services/Types.kt index afab0fe30..5d79a4e62 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/services/Types.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/services/Types.kt @@ -217,6 +217,56 @@ data class CreateDocumentBody( val visibleToClients: Boolean? = null ) +/** Options for GetEverythingBoosts. */ +data class GetEverythingBoostsOptions( + val page: Long? = null, + val maxItems: Int? = null +) { + fun toPaginationOptions(): PaginationOptions = PaginationOptions(maxItems = maxItems) +} + +/** Options for GetEverythingCheckins. */ +data class GetEverythingCheckinsOptions( + val page: Long? = null, + val maxItems: Int? = null +) { + fun toPaginationOptions(): PaginationOptions = PaginationOptions(maxItems = maxItems) +} + +/** Options for GetEverythingComments. */ +data class GetEverythingCommentsOptions( + val page: Long? = null, + val maxItems: Int? = null +) { + fun toPaginationOptions(): PaginationOptions = PaginationOptions(maxItems = maxItems) +} + +/** Options for GetEverythingFiles. */ +data class GetEverythingFilesOptions( + val kind: String? = null, + val peopleIds: List? = null, + val page: Long? = null, + val maxItems: Int? = null +) { + fun toPaginationOptions(): PaginationOptions = PaginationOptions(maxItems = maxItems) +} + +/** Options for GetEverythingForwards. */ +data class GetEverythingForwardsOptions( + val page: Long? = null, + val maxItems: Int? = null +) { + fun toPaginationOptions(): PaginationOptions = PaginationOptions(maxItems = maxItems) +} + +/** Options for GetEverythingMessages. */ +data class GetEverythingMessagesOptions( + val page: Long? = null, + val maxItems: Int? = null +) { + fun toPaginationOptions(): PaginationOptions = PaginationOptions(maxItems = maxItems) +} + /** Request body for CreateForwardReply. */ data class CreateForwardReplyBody( val content: String diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/services/everything.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/services/everything.kt new file mode 100644 index 000000000..d99f78d12 --- /dev/null +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/services/everything.kt @@ -0,0 +1,192 @@ +package com.basecamp.sdk.generated.services + +import com.basecamp.sdk.* +import com.basecamp.sdk.generated.models.* +import com.basecamp.sdk.services.BaseService +import kotlinx.serialization.json.JsonElement + +/** + * Service for Everything operations. + * + * @generated from OpenAPI spec — do not edit directly + */ +class EverythingService(client: AccountClient) : BaseService(client) { + + /** + * Get every boost across all accessible projects, newest-first (paginated). + * @param options Optional query parameters and pagination control + */ + suspend fun everythingBoosts(options: GetEverythingBoostsOptions? = null): ListResult { + val info = OperationInfo( + service = "Everything", + operation = "GetEverythingBoosts", + resourceType = "everything_boost", + isMutation = false, + projectId = null, + resourceId = null, + ) + val qs = buildQueryString( + "page" to options?.page, + ) + return requestPaginated(info, options?.toPaginationOptions(), { + httpGet("/boosts.json" + qs, operationName = info.operation) + }) { body -> + json.decodeFromString>(body) + } + } + + /** + * Get every overdue card across all accessible projects, oldest-due-date-first. + */ + suspend fun everythingOverdueCards(): List { + val info = OperationInfo( + service = "Everything", + operation = "GetEverythingOverdueCards", + resourceType = "everything_overdue_card", + isMutation = false, + projectId = null, + resourceId = null, + ) + return request(info, { + httpGet("/cards/overdue.json", operationName = info.operation) + }) { body -> + json.decodeFromString>(body) + } + } + + /** + * Get every automatic check-in answer across all accessible projects, + * @param options Optional query parameters and pagination control + */ + suspend fun everythingCheckins(options: GetEverythingCheckinsOptions? = null): ListResult { + val info = OperationInfo( + service = "Everything", + operation = "GetEverythingCheckins", + resourceType = "everything_checkin", + isMutation = false, + projectId = null, + resourceId = null, + ) + val qs = buildQueryString( + "page" to options?.page, + ) + return requestPaginated(info, options?.toPaginationOptions(), { + httpGet("/checkins.json" + qs, operationName = info.operation) + }) { body -> + json.decodeFromString>(body) + } + } + + /** + * Get every comment across all accessible projects, newest-first (paginated). + * @param options Optional query parameters and pagination control + */ + suspend fun everythingComments(options: GetEverythingCommentsOptions? = null): ListResult { + val info = OperationInfo( + service = "Everything", + operation = "GetEverythingComments", + resourceType = "everything_comment", + isMutation = false, + projectId = null, + resourceId = null, + ) + val qs = buildQueryString( + "page" to options?.page, + ) + return requestPaginated(info, options?.toPaginationOptions(), { + httpGet("/comments.json" + qs, operationName = info.operation) + }) { body -> + json.decodeFromString>(body) + } + } + + /** + * Get every file recording across all accessible projects, newest-first + * @param options Optional query parameters and pagination control + */ + suspend fun everythingFiles(options: GetEverythingFilesOptions? = null): ListResult { + val info = OperationInfo( + service = "Everything", + operation = "GetEverythingFiles", + resourceType = "everything_file", + isMutation = false, + projectId = null, + resourceId = null, + ) + val qs = buildQueryString( + "kind" to options?.kind, + "people_ids[]" to options?.peopleIds, + "page" to options?.page, + ) + return requestPaginated(info, options?.toPaginationOptions(), { + httpGet("/files.json" + qs, operationName = info.operation) + }) { body -> + json.decodeFromString>(body) + } + } + + /** + * Get every inbox forward across all accessible projects, newest-first + * @param options Optional query parameters and pagination control + */ + suspend fun everythingForwards(options: GetEverythingForwardsOptions? = null): ListResult { + val info = OperationInfo( + service = "Everything", + operation = "GetEverythingForwards", + resourceType = "everything_forward", + isMutation = false, + projectId = null, + resourceId = null, + ) + val qs = buildQueryString( + "page" to options?.page, + ) + return requestPaginated(info, options?.toPaginationOptions(), { + httpGet("/forwards.json" + qs, operationName = info.operation) + }) { body -> + json.decodeFromString>(body) + } + } + + /** + * Get every message across all accessible projects, newest-first (paginated). + * @param options Optional query parameters and pagination control + */ + suspend fun everythingMessages(options: GetEverythingMessagesOptions? = null): ListResult { + val info = OperationInfo( + service = "Everything", + operation = "GetEverythingMessages", + resourceType = "everything_message", + isMutation = false, + projectId = null, + resourceId = null, + ) + val qs = buildQueryString( + "page" to options?.page, + ) + return requestPaginated(info, options?.toPaginationOptions(), { + httpGet("/messages.json" + qs, operationName = info.operation) + }) { body -> + json.decodeFromString>(body) + } + } + + /** + * Get every overdue to-do across all accessible projects, oldest-due-date-first. + */ + suspend fun everythingOverdueTodos(): List { + val info = OperationInfo( + service = "Everything", + operation = "GetEverythingOverdueTodos", + resourceType = "everything_overdue_todo", + isMutation = false, + projectId = null, + resourceId = null, + ) + return request(info, { + httpGet("/todos/overdue.json", operationName = info.operation) + }) { body -> + json.decodeFromString>(body) + } + } +} diff --git a/kotlin/sdk/src/commonTest/kotlin/com/basecamp/sdk/EverythingFilesDecodeTest.kt b/kotlin/sdk/src/commonTest/kotlin/com/basecamp/sdk/EverythingFilesDecodeTest.kt new file mode 100644 index 000000000..9d7b2b24d --- /dev/null +++ b/kotlin/sdk/src/commonTest/kotlin/com/basecamp/sdk/EverythingFilesDecodeTest.kt @@ -0,0 +1,103 @@ +package com.basecamp.sdk + +import com.basecamp.sdk.generated.models.EverythingFile +import kotlinx.serialization.json.Json +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull + +class EverythingFilesDecodeTest { + + private val json = Json { ignoreUnknownKeys = true } + + // Canonical /files.json "everything" feed: a heterogeneous superset carrying a full + // Upload recording, a Basecamp Document recording, and a rich-text Attachment envelope + // in a single array. everythingFiles(options) decodes this as List; here + // we prove the generated EverythingFile superset decodes every variant's real per-variant + // fields at runtime. + private val fixtureJson = """ + [ + { + "id": 900, + "type": "Upload", + "status": "active", + "visible_to_clients": false, + "title": "logo.png", + "inherits_status": true, + "filename": "logo.png", + "content_type": "image/png", + "byte_size": 1281, + "width": 1024.0, + "height": 768.0, + "url": "https://3.basecampapi.com/1/buckets/2/uploads/900.json", + "app_url": "https://3.basecamp.com/1/buckets/2/uploads/900", + "download_url": "https://3.basecampapi.com/1/buckets/2/uploads/900/download/logo.png", + "app_download_url": "https://storage.3.basecamp.com/1/buckets/2/uploads/900/download/logo.png", + "bucket": { "id": 2, "name": "The Leto Laptop", "type": "Project" }, + "creator": { "id": 1, "name": "Victor Cooper" } + }, + { + "id": 901, + "type": "Document", + "status": "active", + "visible_to_clients": false, + "title": "Spec", + "inherits_status": true, + "content_type": "text/html", + "url": "https://3.basecampapi.com/1/buckets/2/documents/901.json", + "app_url": "https://3.basecamp.com/1/buckets/2/documents/901", + "bucket": { "id": 2, "name": "The Leto Laptop", "type": "Project" }, + "creator": { "id": 1, "name": "Victor Cooper" } + }, + { + "id": 902, + "type": "Attachment", + "attachable_sgid": "sgid-902", + "filename": "chart.avif", + "content_type": "image/avif", + "byte_size": 4096, + "width": null, + "height": null, + "download_url": "https://storage.3.basecamp.com/1/blobs/902/download/chart.avif", + "parent": { + "id": 800, + "title": "A message", + "type": "Message", + "url": "https://3.basecampapi.com/1/buckets/2/messages/800.json", + "app_url": "https://3.basecamp.com/1/buckets/2/messages/800" + } + } + ] + """.trimIndent() + + @Test + fun decodesHeterogeneousEverythingFilesFeed() { + val files = json.decodeFromString>(fixtureJson) + + assertEquals(3, files.size) + + // Variant 0: full Upload recording; FlexibleIntSerializer collapses 1024.0 -> 1024. + assertEquals("Upload", files[0].type) + assertEquals("logo.png", files[0].filename) + assertNotNull(files[0].appDownloadUrl) + assertEquals( + "https://storage.3.basecamp.com/1/buckets/2/uploads/900/download/logo.png", + files[0].appDownloadUrl, + ) + assertEquals(1024, files[0].width) + + // Variant 1: Basecamp Document recording. + assertEquals("Document", files[1].type) + assertEquals("Spec", files[1].title) + + // Variant 2: rich-text Attachment envelope; null image dimensions decode to null. + assertEquals("Attachment", files[2].type) + assertEquals("sgid-902", files[2].attachableSgid) + assertEquals("chart.avif", files[2].filename) + assertNull(files[2].width) + // The attachment envelope identifies the doc/message the file lives in. + assertNotNull(files[2].parent) + assertEquals("Message", files[2].parent?.type) + } +} diff --git a/openapi.json b/openapi.json index 36e3f7bf9..abd0dc878 100644 --- a/openapi.json +++ b/openapi.json @@ -492,6 +492,104 @@ } } }, + "/{accountId}/boosts.json": { + "get": { + "description": "Get every boost across all accessible projects, newest-first (paginated).\nEach boost carries its `booster` and the `recording` it boosts.", + "operationId": "GetEverythingBoosts", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "GetEverythingBoosts 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetEverythingBoostsResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Everything" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, "/{accountId}/boosts/{boostId}": { "delete": { "description": "Delete a boost", @@ -3449,6 +3547,79 @@ } } }, + "/{accountId}/cards/overdue.json": { + "get": { + "description": "Get every overdue card across all accessible projects, oldest-due-date-first.\nA complete, unpaginated array; each item embeds its `bucket`.", + "operationId": "GetEverythingOverdueCards", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetEverythingOverdueCards 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetEverythingOverdueCardsResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Everything" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, "/{accountId}/categories.json": { "get": { "description": "List message types in a project\n\n**Pagination**: Uses Link header (RFC5988). Follow the `next` rel URL\nto fetch additional pages. X-Total-Count header provides total count.", @@ -5414,6 +5585,104 @@ } } }, + "/{accountId}/checkins.json": { + "get": { + "description": "Get every automatic check-in answer across all accessible projects,\nnewest-first (paginated). Each item embeds its `bucket`.", + "operationId": "GetEverythingCheckins", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "GetEverythingCheckins 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetEverythingCheckinsResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Everything" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, "/{accountId}/circles/people.json": { "get": { "description": "List all account users who can be pinged\n\n**Pagination**: Uses Link header (RFC5988). Follow the `next` rel URL\nto fetch additional pages. X-Total-Count header provides total count.", @@ -6096,10 +6365,10 @@ } } }, - "/{accountId}/comments/{commentId}": { + "/{accountId}/comments.json": { "get": { - "description": "Get a single comment by id", - "operationId": "GetComment", + "description": "Get every comment across all accessible projects, newest-first (paginated).\nEach item embeds its `bucket`.", + "operationId": "GetEverythingComments", "parameters": [ { "name": "accountId", @@ -6113,12 +6382,110 @@ "required": true }, { - "name": "commentId", - "in": "path", + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1.", "schema": { "type": "integer", - "format": "int64" - }, + "description": "Page number for paginating through results. Defaults to 1.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "GetEverythingComments 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetEverythingCommentsResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Everything" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/comments/{commentId}": { + "get": { + "description": "Get a single comment by id", + "operationId": "GetComment", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "commentId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, "required": true } ], @@ -6796,6 +7163,227 @@ } } }, + "/{accountId}/files.json": { + "get": { + "description": "Get every file recording across all accessible projects, newest-first\n(paginated). Heterogeneous: uploads and Basecamp documents carry their\nstandard recording shapes, while rich-text attachments are wrapped in a\nrecording envelope plus an `attachable_sgid` and blob metadata. Modeled as\nan optional-field superset (EverythingFile) so one element type decodes any\nvariant.", + "operationId": "GetEverythingFiles", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "kind", + "in": "query", + "description": "Filter by file kind: all (default), images, pdfs, documents, or videos.", + "schema": { + "type": "string", + "description": "Filter by file kind: all (default), images, pdfs, documents, or videos." + } + }, + { + "name": "people_ids[]", + "in": "query", + "description": "Restrict to files created by the given people (repeatable).", + "style": "form", + "schema": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + }, + "description": "Restrict to files created by the given people (repeatable).", + "x-go-type-skip-optional-pointer": false + }, + "explode": true + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "GetEverythingFiles 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetEverythingFilesResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Everything" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/forwards.json": { + "get": { + "description": "Get every inbox forward across all accessible projects, newest-first\n(paginated). Each item embeds its `bucket`.", + "operationId": "GetEverythingForwards", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "GetEverythingForwards 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetEverythingForwardsResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Everything" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, "/{accountId}/gauge_needles/{needleId}": { "delete": { "description": "Destroy a gauge needle", @@ -8355,16 +8943,109 @@ } } }, - "422": { - "description": "ValidationError 422 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ValidationErrorResponseContent" - } - } - } - }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Messages" + ], + "x-basecamp-retry": { + "maxAttempts": 2, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/{accountId}/messages.json": { + "get": { + "description": "Get every message across all accessible projects, newest-first (paginated).\nEach item embeds its `bucket` for project context.", + "operationId": "GetEverythingMessages", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "GetEverythingMessages 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetEverythingMessagesResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, "429": { "description": "RateLimitError 429 response", "content": { @@ -8387,10 +9068,15 @@ } }, "tags": [ - "Messages" + "Everything" ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, "x-basecamp-retry": { - "maxAttempts": 2, + "maxAttempts": 3, "baseDelayMs": 1000, "backoff": "exponential", "retryOn": [ @@ -18888,6 +19574,79 @@ } } }, + "/{accountId}/todos/overdue.json": { + "get": { + "description": "Get every overdue to-do across all accessible projects, oldest-due-date-first.\nA complete, unpaginated array; each item embeds its `bucket`.", + "operationId": "GetEverythingOverdueTodos", + "parameters": [ + { + "name": "accountId", + "in": "path", + "description": "Basecamp account ID (numeric string)", + "schema": { + "type": "string", + "pattern": "^[0-9]+$", + "description": "Basecamp account ID (numeric string)" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetEverythingOverdueTodos 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetEverythingOverdueTodosResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Everything" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, "/{accountId}/todos/{todoId}": { "delete": { "description": "Trash a todo (returns 204 No Content)", @@ -23957,6 +24716,144 @@ } } }, + "EverythingFile": { + "type": "object", + "description": "A single item in the /files.json feed. An optional-field superset over three\nwire variants — a full Upload recording, a Basecamp Document recording, and a\nrich-text attachment wrapped in a recording envelope (distinguished by\n`attachable_sgid` and blob metadata). Every field is optional; a given\ninstance populates only the fields of the variant it represents. Unknown\nfields are ignored by every SDK decoder, so the superset need not enumerate\nevery field of the Upload/Document recordings.", + "properties": { + "id": { + "type": "integer", + "description": "Recording (Upload/Document) or attachment id.", + "format": "int64", + "x-go-type-skip-optional-pointer": false + }, + "status": { + "type": "string" + }, + "visible_to_clients": { + "type": "boolean", + "x-go-type-skip-optional-pointer": false + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + }, + "x-go-type-skip-optional-pointer": false + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + }, + "x-go-type-skip-optional-pointer": false + }, + "title": { + "type": "string" + }, + "inherits_status": { + "type": "boolean", + "x-go-type-skip-optional-pointer": false + }, + "type": { + "type": "string", + "description": "\"Upload\", \"Document\", or \"Attachment\"." + }, + "url": { + "type": "string" + }, + "app_url": { + "type": "string" + }, + "bookmark_url": { + "type": "string" + }, + "subscription_url": { + "type": "string" + }, + "comments_count": { + "type": "integer", + "format": "int32" + }, + "comments_url": { + "type": "string" + }, + "boosts_count": { + "type": "integer", + "format": "int32" + }, + "boosts_url": { + "type": "string" + }, + "position": { + "type": "integer", + "format": "int32" + }, + "parent": { + "$ref": "#/components/schemas/RecordingParent" + }, + "bucket": { + "$ref": "#/components/schemas/RecordingBucket" + }, + "creator": { + "$ref": "#/components/schemas/Person" + }, + "attachable_sgid": { + "type": "string", + "description": "Present on the rich-text attachment variant: signed global id of the\nattachment (uploads/documents omit it)." + }, + "content_type": { + "type": "string" + }, + "byte_size": { + "type": "integer", + "format": "int64" + }, + "filename": { + "type": "string" + }, + "download_url": { + "type": "string", + "x-basecamp-auth-routable-url": {} + }, + "app_download_url": { + "type": "string" + }, + "width": { + "type": "integer", + "description": "Pixel width; null for non-image blobs and may be float-spelled (1024.0).", + "format": "int32", + "nullable": true, + "x-go-type": "types.FlexInt", + "x-go-type-import": { + "path": "github.com/basecamp/basecamp-sdk/go/pkg/types" + }, + "x-go-type-skip-optional-pointer": false + }, + "height": { + "type": "integer", + "description": "Pixel height; null for non-image blobs and may be float-spelled (1024.0).", + "format": "int32", + "nullable": true, + "x-go-type": "types.FlexInt", + "x-go-type-import": { + "path": "github.com/basecamp/basecamp-sdk/go/pkg/types" + }, + "x-go-type-skip-optional-pointer": false + }, + "description": { + "type": "string", + "description": "Rich-text description (upload/document variants)." + }, + "description_attachments": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RichTextAttachment" + } + } + } + }, "FirstWeekDay": { "type": "string", "enum": [ @@ -24474,6 +25371,54 @@ "GetDocumentResponseContent": { "$ref": "#/components/schemas/Document" }, + "GetEverythingBoostsResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Boost" + } + }, + "GetEverythingCheckinsResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Recording" + } + }, + "GetEverythingCommentsResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Recording" + } + }, + "GetEverythingFilesResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EverythingFile" + } + }, + "GetEverythingForwardsResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Recording" + } + }, + "GetEverythingMessagesResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Recording" + } + }, + "GetEverythingOverdueCardsResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Card" + } + }, + "GetEverythingOverdueTodosResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Todo" + } + }, "GetForwardReplyResponseContent": { "$ref": "#/components/schemas/ForwardReply" }, diff --git a/python/scripts/generate_services.py b/python/scripts/generate_services.py index 203c2ead1..0b573324a 100644 --- a/python/scripts/generate_services.py +++ b/python/scripts/generate_services.py @@ -454,7 +454,13 @@ def parse_operation( success = operation.get("responses", {}).get("200") or operation.get("responses", {}).get("201") response_schema = (success or {}).get("content", {}).get("application/json", {}).get("schema") returns_void = response_schema is None - returns_array = (response_schema or {}).get("type") == "array" + # Resolve through a $ref so bare-array ResponseContent aliases (e.g. the + # unpaginated overdue lists, whose response is a $ref to `Todo[]`) are + # detected as arrays rather than falling through to a dict return type. + resolved_response = response_schema + if isinstance(response_schema, dict) and "$ref" in response_schema: + resolved_response = resolve_schema_ref(response_schema, schemas) or response_schema + returns_array = (resolved_response or {}).get("type") == "array" # Pagination pagination = operation.get("x-basecamp-pagination") diff --git a/python/src/basecamp/async_client.py b/python/src/basecamp/async_client.py index 7dc4d9e4b..be41eb6af 100644 --- a/python/src/basecamp/async_client.py +++ b/python/src/basecamp/async_client.py @@ -349,6 +349,12 @@ def timeline(self): return self._service("timeline", lambda: AsyncTimelineService(self)) + @property + def everything(self): + from basecamp.generated.services.everything import AsyncEverythingService + + return self._service("everything", lambda: AsyncEverythingService(self)) + @property def tools(self): from basecamp.generated.services.tools import AsyncToolsService diff --git a/python/src/basecamp/client.py b/python/src/basecamp/client.py index 80c496e3e..ad308a821 100644 --- a/python/src/basecamp/client.py +++ b/python/src/basecamp/client.py @@ -350,6 +350,12 @@ def timeline(self): return self._service("timeline", lambda: TimelineService(self)) + @property + def everything(self): + from basecamp.generated.services.everything import EverythingService + + return self._service("everything", lambda: EverythingService(self)) + @property def tools(self): from basecamp.generated.services.tools import ToolsService diff --git a/python/src/basecamp/generated/metadata.json b/python/src/basecamp/generated/metadata.json index c6cf346a4..3ca2959fc 100644 --- a/python/src/basecamp/generated/metadata.json +++ b/python/src/basecamp/generated/metadata.json @@ -751,6 +751,94 @@ ] } }, + "GetEverythingBoosts": { + "retry": { + "backoff": "exponential", + "base_delay_ms": 1000, + "max": 3, + "retry_on": [ + 429, + 503 + ] + } + }, + "GetEverythingCheckins": { + "retry": { + "backoff": "exponential", + "base_delay_ms": 1000, + "max": 3, + "retry_on": [ + 429, + 503 + ] + } + }, + "GetEverythingComments": { + "retry": { + "backoff": "exponential", + "base_delay_ms": 1000, + "max": 3, + "retry_on": [ + 429, + 503 + ] + } + }, + "GetEverythingFiles": { + "retry": { + "backoff": "exponential", + "base_delay_ms": 1000, + "max": 3, + "retry_on": [ + 429, + 503 + ] + } + }, + "GetEverythingForwards": { + "retry": { + "backoff": "exponential", + "base_delay_ms": 1000, + "max": 3, + "retry_on": [ + 429, + 503 + ] + } + }, + "GetEverythingMessages": { + "retry": { + "backoff": "exponential", + "base_delay_ms": 1000, + "max": 3, + "retry_on": [ + 429, + 503 + ] + } + }, + "GetEverythingOverdueCards": { + "retry": { + "backoff": "exponential", + "base_delay_ms": 1000, + "max": 3, + "retry_on": [ + 429, + 503 + ] + } + }, + "GetEverythingOverdueTodos": { + "retry": { + "backoff": "exponential", + "base_delay_ms": 1000, + "max": 3, + "retry_on": [ + 429, + 503 + ] + } + }, "GetForward": { "retry": { "backoff": "exponential", diff --git a/python/src/basecamp/generated/services/__init__.py b/python/src/basecamp/generated/services/__init__.py index 9466a9cfd..23ba27a96 100644 --- a/python/src/basecamp/generated/services/__init__.py +++ b/python/src/basecamp/generated/services/__init__.py @@ -20,6 +20,7 @@ from basecamp.generated.services.comments import CommentsService, AsyncCommentsService from basecamp.generated.services.documents import DocumentsService, AsyncDocumentsService from basecamp.generated.services.events import EventsService, AsyncEventsService +from basecamp.generated.services.everything import EverythingService, AsyncEverythingService from basecamp.generated.services.forwards import ForwardsService, AsyncForwardsService from basecamp.generated.services.gauges import GaugesService, AsyncGaugesService from basecamp.generated.services.hill_charts import HillChartsService, AsyncHillChartsService @@ -84,6 +85,8 @@ "AsyncDocumentsService", "EventsService", "AsyncEventsService", + "EverythingService", + "AsyncEverythingService", "ForwardsService", "AsyncForwardsService", "GaugesService", diff --git a/python/src/basecamp/generated/services/automation.py b/python/src/basecamp/generated/services/automation.py index a3f00316b..392d668f4 100644 --- a/python/src/basecamp/generated/services/automation.py +++ b/python/src/basecamp/generated/services/automation.py @@ -11,18 +11,16 @@ class AutomationService(BaseService): - def list_lineup_markers(self) -> dict[str, Any]: - return self._request( + def list_lineup_markers(self) -> ListResult: + return self._request_paginated( OperationInfo(service="automation", operation="list_lineup_markers", is_mutation=False), - "GET", "/lineup/markers.json", ) class AsyncAutomationService(AsyncBaseService): - async def list_lineup_markers(self) -> dict[str, Any]: - return await self._request( + async def list_lineup_markers(self) -> ListResult: + return await self._request_paginated( OperationInfo(service="automation", operation="list_lineup_markers", is_mutation=False), - "GET", "/lineup/markers.json", ) diff --git a/python/src/basecamp/generated/services/everything.py b/python/src/basecamp/generated/services/everything.py new file mode 100644 index 000000000..eaa9b70fa --- /dev/null +++ b/python/src/basecamp/generated/services/everything.py @@ -0,0 +1,126 @@ +# @generated from OpenAPI spec — do not edit manually + +from __future__ import annotations + +from typing import Any + +from basecamp.generated.services._base import BaseService +from basecamp.generated.services._async_base import AsyncBaseService +from basecamp._pagination import ListResult +from basecamp.hooks import OperationInfo + + +class EverythingService(BaseService): + def get_everything_boosts(self, *, page: int | None = None) -> ListResult: + return self._request_paginated( + OperationInfo(service="everything", operation="get_everything_boosts", is_mutation=False), + "/boosts.json", + params=self._compact(page=page), + ) + + def get_everything_overdue_cards(self) -> ListResult: + return self._request_paginated( + OperationInfo(service="everything", operation="get_everything_overdue_cards", is_mutation=False), + "/cards/overdue.json", + ) + + def get_everything_checkins(self, *, page: int | None = None) -> ListResult: + return self._request_paginated( + OperationInfo(service="everything", operation="get_everything_checkins", is_mutation=False), + "/checkins.json", + params=self._compact(page=page), + ) + + def get_everything_comments(self, *, page: int | None = None) -> ListResult: + return self._request_paginated( + OperationInfo(service="everything", operation="get_everything_comments", is_mutation=False), + "/comments.json", + params=self._compact(page=page), + ) + + def get_everything_files( + self, *, kind: str | None = None, people_ids: list[int] | None = None, page: int | None = None + ) -> ListResult: + return self._request_paginated( + OperationInfo(service="everything", operation="get_everything_files", is_mutation=False), + "/files.json", + params={k: v for k, v in {"kind": kind, "people_ids[]": people_ids, "page": page}.items() if v is not None}, + ) + + def get_everything_forwards(self, *, page: int | None = None) -> ListResult: + return self._request_paginated( + OperationInfo(service="everything", operation="get_everything_forwards", is_mutation=False), + "/forwards.json", + params=self._compact(page=page), + ) + + def get_everything_messages(self, *, page: int | None = None) -> ListResult: + return self._request_paginated( + OperationInfo(service="everything", operation="get_everything_messages", is_mutation=False), + "/messages.json", + params=self._compact(page=page), + ) + + def get_everything_overdue_todos(self) -> ListResult: + return self._request_paginated( + OperationInfo(service="everything", operation="get_everything_overdue_todos", is_mutation=False), + "/todos/overdue.json", + ) + + +class AsyncEverythingService(AsyncBaseService): + async def get_everything_boosts(self, *, page: int | None = None) -> ListResult: + return await self._request_paginated( + OperationInfo(service="everything", operation="get_everything_boosts", is_mutation=False), + "/boosts.json", + params=self._compact(page=page), + ) + + async def get_everything_overdue_cards(self) -> ListResult: + return await self._request_paginated( + OperationInfo(service="everything", operation="get_everything_overdue_cards", is_mutation=False), + "/cards/overdue.json", + ) + + async def get_everything_checkins(self, *, page: int | None = None) -> ListResult: + return await self._request_paginated( + OperationInfo(service="everything", operation="get_everything_checkins", is_mutation=False), + "/checkins.json", + params=self._compact(page=page), + ) + + async def get_everything_comments(self, *, page: int | None = None) -> ListResult: + return await self._request_paginated( + OperationInfo(service="everything", operation="get_everything_comments", is_mutation=False), + "/comments.json", + params=self._compact(page=page), + ) + + async def get_everything_files( + self, *, kind: str | None = None, people_ids: list[int] | None = None, page: int | None = None + ) -> ListResult: + return await self._request_paginated( + OperationInfo(service="everything", operation="get_everything_files", is_mutation=False), + "/files.json", + params={k: v for k, v in {"kind": kind, "people_ids[]": people_ids, "page": page}.items() if v is not None}, + ) + + async def get_everything_forwards(self, *, page: int | None = None) -> ListResult: + return await self._request_paginated( + OperationInfo(service="everything", operation="get_everything_forwards", is_mutation=False), + "/forwards.json", + params=self._compact(page=page), + ) + + async def get_everything_messages(self, *, page: int | None = None) -> ListResult: + return await self._request_paginated( + OperationInfo(service="everything", operation="get_everything_messages", is_mutation=False), + "/messages.json", + params=self._compact(page=page), + ) + + async def get_everything_overdue_todos(self) -> ListResult: + return await self._request_paginated( + OperationInfo(service="everything", operation="get_everything_overdue_todos", is_mutation=False), + "/todos/overdue.json", + ) diff --git a/python/src/basecamp/generated/services/my_assignments.py b/python/src/basecamp/generated/services/my_assignments.py index 6bf12169d..b90ef8d9c 100644 --- a/python/src/basecamp/generated/services/my_assignments.py +++ b/python/src/basecamp/generated/services/my_assignments.py @@ -18,17 +18,15 @@ def get_my_assignments(self) -> dict[str, Any]: "/my/assignments.json", ) - def get_my_completed_assignments(self) -> dict[str, Any]: - return self._request( + def get_my_completed_assignments(self) -> ListResult: + return self._request_paginated( OperationInfo(service="myassignments", operation="get_my_completed_assignments", is_mutation=False), - "GET", "/my/assignments/completed.json", ) - def get_my_due_assignments(self, *, scope: str | None = None) -> dict[str, Any]: - return self._request( + def get_my_due_assignments(self, *, scope: str | None = None) -> ListResult: + return self._request_paginated( OperationInfo(service="myassignments", operation="get_my_due_assignments", is_mutation=False), - "GET", "/my/assignments/due.json", params=self._compact(scope=scope), ) @@ -42,17 +40,15 @@ async def get_my_assignments(self) -> dict[str, Any]: "/my/assignments.json", ) - async def get_my_completed_assignments(self) -> dict[str, Any]: - return await self._request( + async def get_my_completed_assignments(self) -> ListResult: + return await self._request_paginated( OperationInfo(service="myassignments", operation="get_my_completed_assignments", is_mutation=False), - "GET", "/my/assignments/completed.json", ) - async def get_my_due_assignments(self, *, scope: str | None = None) -> dict[str, Any]: - return await self._request( + async def get_my_due_assignments(self, *, scope: str | None = None) -> ListResult: + return await self._request_paginated( OperationInfo(service="myassignments", operation="get_my_due_assignments", is_mutation=False), - "GET", "/my/assignments/due.json", params=self._compact(scope=scope), ) diff --git a/python/src/basecamp/generated/services/people.py b/python/src/basecamp/generated/services/people.py index 1b5bf953d..7df9595fa 100644 --- a/python/src/basecamp/generated/services/people.py +++ b/python/src/basecamp/generated/services/people.py @@ -124,10 +124,9 @@ def update_project_access( operation="UpdateProjectAccess", ) - def list_assignable(self) -> dict[str, Any]: - return self._request( + def list_assignable(self) -> ListResult: + return self._request_paginated( OperationInfo(service="people", operation="list_assignable", is_mutation=False), - "GET", "/reports/todos/assigned.json", ) @@ -246,9 +245,8 @@ async def update_project_access( operation="UpdateProjectAccess", ) - async def list_assignable(self) -> dict[str, Any]: - return await self._request( + async def list_assignable(self) -> ListResult: + return await self._request_paginated( OperationInfo(service="people", operation="list_assignable", is_mutation=False), - "GET", "/reports/todos/assigned.json", ) diff --git a/python/src/basecamp/generated/services/timesheets.py b/python/src/basecamp/generated/services/timesheets.py index a2667cdea..35e754500 100644 --- a/python/src/basecamp/generated/services/timesheets.py +++ b/python/src/basecamp/generated/services/timesheets.py @@ -40,12 +40,9 @@ def create( operation="CreateTimesheetEntry", ) - def report( - self, *, from_: str | None = None, to: str | None = None, person_id: int | None = None - ) -> dict[str, Any]: - return self._request( + def report(self, *, from_: str | None = None, to: str | None = None, person_id: int | None = None) -> ListResult: + return self._request_paginated( OperationInfo(service="timesheets", operation="report", is_mutation=False), - "GET", "/reports/timesheet.json", params={k: v for k, v in {"from": from_, "to": to, "person_id": person_id}.items() if v is not None}, ) @@ -107,10 +104,9 @@ async def create( async def report( self, *, from_: str | None = None, to: str | None = None, person_id: int | None = None - ) -> dict[str, Any]: - return await self._request( + ) -> ListResult: + return await self._request_paginated( OperationInfo(service="timesheets", operation="report", is_mutation=False), - "GET", "/reports/timesheet.json", params={k: v for k, v in {"from": from_, "to": to, "person_id": person_id}.items() if v is not None}, ) diff --git a/python/src/basecamp/generated/types.py b/python/src/basecamp/generated/types.py index 7f63b48b0..a8dac6fea 100644 --- a/python/src/basecamp/generated/types.py +++ b/python/src/basecamp/generated/types.py @@ -613,6 +613,39 @@ class EventDetails(TypedDict): removed_person_ids: NotRequired[list[int]] +class EverythingFile(TypedDict): + app_download_url: NotRequired[str] + app_url: NotRequired[str] + attachable_sgid: NotRequired[str] + bookmark_url: NotRequired[str] + boosts_count: NotRequired[int] + boosts_url: NotRequired[str] + bucket: NotRequired[RecordingBucket] + byte_size: NotRequired[int] + comments_count: NotRequired[int] + comments_url: NotRequired[str] + content_type: NotRequired[str] + created_at: NotRequired[str] + creator: NotRequired[Person] + description: NotRequired[str] + description_attachments: NotRequired[list[RichTextAttachment]] + download_url: NotRequired[str] + filename: NotRequired[str] + height: NotRequired[Optional[int | float]] + id: NotRequired[int] + inherits_status: NotRequired[bool] + parent: NotRequired[RecordingParent] + position: NotRequired[int] + status: NotRequired[str] + subscription_url: NotRequired[str] + title: NotRequired[str] + type: NotRequired[str] + updated_at: NotRequired[str] + url: NotRequired[str] + visible_to_clients: NotRequired[bool] + width: NotRequired[Optional[int | float]] + + class ForbiddenErrorResponseContent(TypedDict): error: str message: NotRequired[str] diff --git a/python/tests/services/test_everything.py b/python/tests/services/test_everything.py new file mode 100644 index 000000000..eeffe4ab5 --- /dev/null +++ b/python/tests/services/test_everything.py @@ -0,0 +1,352 @@ +"""Tests for the heterogeneous /files.json "everything" feed.""" + +from __future__ import annotations + +import httpx +import respx + +from basecamp import Client + +# Canonical heterogeneous feed: a full Upload recording, a Basecamp Document +# recording, and a rich-text Attachment envelope in a single non-empty array. +_FILES_FEED = [ + { + "id": 900, + "type": "Upload", + "status": "active", + "visible_to_clients": False, + "title": "logo.png", + "inherits_status": True, + "filename": "logo.png", + "content_type": "image/png", + "byte_size": 1281, + "width": 1024.0, + "height": 768.0, + "url": "https://3.basecampapi.com/1/buckets/2/uploads/900.json", + "app_url": "https://3.basecamp.com/1/buckets/2/uploads/900", + "download_url": "https://3.basecampapi.com/1/buckets/2/uploads/900/download/logo.png", + "app_download_url": "https://storage.3.basecamp.com/1/buckets/2/uploads/900/download/logo.png", + "bucket": {"id": 2, "name": "The Leto Laptop", "type": "Project"}, + "creator": {"id": 1, "name": "Victor Cooper"}, + }, + { + "id": 901, + "type": "Document", + "status": "active", + "visible_to_clients": False, + "title": "Spec", + "inherits_status": True, + "content_type": "text/html", + "url": "https://3.basecampapi.com/1/buckets/2/documents/901.json", + "app_url": "https://3.basecamp.com/1/buckets/2/documents/901", + "bucket": {"id": 2, "name": "The Leto Laptop", "type": "Project"}, + "creator": {"id": 1, "name": "Victor Cooper"}, + }, + { + "id": 902, + "type": "Attachment", + "attachable_sgid": "sgid-902", + "filename": "chart.avif", + "content_type": "image/avif", + "byte_size": 4096, + "width": None, + "height": None, + "download_url": "https://storage.3.basecamp.com/1/blobs/902/download/chart.avif", + "parent": {"id": 800, "title": "A message", "type": "Message"}, + }, +] + + +class TestEverythingFiles: + @respx.mock + def test_heterogeneous_files_feed_decodes_all_three_variants(self): + route = respx.get("https://3.basecampapi.com/12345/files.json").mock( + return_value=httpx.Response(200, json=_FILES_FEED) + ) + + account = Client(access_token="test-token").for_account("12345") + result = account.everything.get_everything_files(kind=None, people_ids=None) + + assert route.called + assert len(result) == 3 + + upload = result[0] + assert upload["type"] == "Upload" + assert upload["filename"] == "logo.png" + assert "app_download_url" in upload + assert upload["width"] == 1024.0 + + document = result[1] + assert document["type"] == "Document" + assert document["title"] == "Spec" + + attachment = result[2] + assert attachment["type"] == "Attachment" + assert attachment["attachable_sgid"] == "sgid-902" + assert "parent" in attachment + assert attachment["width"] is None + + +# A recording root embeds a `bucket` (the project it lives in). Messages, +# comments, check-in entries, and forwards are all recordings. +_MESSAGES_FEED = [ + { + "id": 1000, + "type": "Message", + "status": "active", + "visible_to_clients": False, + "title": "Kickoff", + "content": "
Let's go
", + "bucket": {"id": 2, "name": "The Leto Laptop", "type": "Project"}, + "creator": {"id": 1, "name": "Victor Cooper"}, + }, + { + "id": 1001, + "type": "Message", + "status": "active", + "visible_to_clients": True, + "title": "Status update", + "content": "
All green
", + "bucket": {"id": 3, "name": "Honcho Design", "type": "Project"}, + "creator": {"id": 1, "name": "Victor Cooper"}, + }, +] + +_COMMENTS_FEED = [ + { + "id": 1100, + "type": "Comment", + "status": "active", + "visible_to_clients": False, + "content": "
Nice work
", + "bucket": {"id": 2, "name": "The Leto Laptop", "type": "Project"}, + "creator": {"id": 1, "name": "Victor Cooper"}, + }, + { + "id": 1101, + "type": "Comment", + "status": "active", + "visible_to_clients": False, + "content": "
Agreed
", + "bucket": {"id": 3, "name": "Honcho Design", "type": "Project"}, + "creator": {"id": 2, "name": "Annie Bryan"}, + }, +] + +_CHECKINS_FEED = [ + { + "id": 1200, + "type": "Question::Answer", + "status": "active", + "visible_to_clients": False, + "content": "
Shipped the SDK tests
", + "bucket": {"id": 2, "name": "The Leto Laptop", "type": "Project"}, + "creator": {"id": 1, "name": "Victor Cooper"}, + }, + { + "id": 1201, + "type": "Question::Answer", + "status": "active", + "visible_to_clients": False, + "content": "
Reviewed PRs
", + "bucket": {"id": 3, "name": "Honcho Design", "type": "Project"}, + "creator": {"id": 2, "name": "Annie Bryan"}, + }, +] + +_FORWARDS_FEED = [ + { + "id": 1300, + "type": "Inbox::Forward", + "status": "active", + "visible_to_clients": False, + "subject": "FW: Invoice", + "bucket": {"id": 2, "name": "The Leto Laptop", "type": "Project"}, + "creator": {"id": 1, "name": "Victor Cooper"}, + }, + { + "id": 1301, + "type": "Inbox::Forward", + "status": "active", + "visible_to_clients": False, + "subject": "FW: Contract", + "bucket": {"id": 3, "name": "Honcho Design", "type": "Project"}, + "creator": {"id": 2, "name": "Annie Bryan"}, + }, +] + +# A boost is a tiny reaction wrapping the `recording` it was applied to. +_BOOSTS_FEED = [ + { + "id": 1400, + "content": "👍", + "created_at": "2024-01-01T00:00:00Z", + "creator": {"id": 1, "name": "Victor Cooper"}, + "recording": {"id": 1000, "type": "Message", "title": "Kickoff"}, + }, + { + "id": 1401, + "content": "🔥", + "created_at": "2024-01-02T00:00:00Z", + "creator": {"id": 2, "name": "Annie Bryan"}, + "recording": {"id": 1100, "type": "Comment", "title": "Nice work"}, + }, +] + +# Overdue feeds are unpaginated bare arrays sorted oldest-first by due_on. +_OVERDUE_TODOS_FEED = [ + { + "id": 1500, + "type": "Todo", + "status": "active", + "visible_to_clients": False, + "title": "Renew domain", + "due_on": "2024-01-01", + "bucket": {"id": 2, "name": "The Leto Laptop", "type": "Project"}, + }, + { + "id": 1501, + "type": "Todo", + "status": "active", + "visible_to_clients": False, + "title": "File taxes", + "due_on": "2024-03-15", + "bucket": {"id": 3, "name": "Honcho Design", "type": "Project"}, + }, +] + +_OVERDUE_CARDS_FEED = [ + { + "id": 1600, + "type": "Kanban::Card", + "status": "active", + "visible_to_clients": False, + "title": "Ship beta", + "due_on": "2024-02-01", + "bucket": {"id": 2, "name": "The Leto Laptop", "type": "Project"}, + }, + { + "id": 1601, + "type": "Kanban::Card", + "status": "active", + "visible_to_clients": False, + "title": "Cut release", + "due_on": "2024-04-01", + "bucket": {"id": 3, "name": "Honcho Design", "type": "Project"}, + }, +] + + +class TestEverythingRecordingFeeds: + @respx.mock + def test_messages_feed_embeds_bucket_roots(self): + route = respx.get("https://3.basecampapi.com/12345/messages.json").mock( + return_value=httpx.Response(200, json=_MESSAGES_FEED) + ) + + account = Client(access_token="test-token").for_account("12345") + result = account.everything.get_everything_messages() + + assert route.called + assert len(result) == 2 + assert result[0]["id"] == 1000 + assert result[0]["type"] == "Message" + assert result[0]["bucket"] == {"id": 2, "name": "The Leto Laptop", "type": "Project"} + assert result[1]["visible_to_clients"] is True + + @respx.mock + def test_comments_feed_embeds_bucket_roots(self): + route = respx.get("https://3.basecampapi.com/12345/comments.json").mock( + return_value=httpx.Response(200, json=_COMMENTS_FEED) + ) + + account = Client(access_token="test-token").for_account("12345") + result = account.everything.get_everything_comments() + + assert route.called + assert len(result) == 2 + assert result[0]["id"] == 1100 + assert result[0]["type"] == "Comment" + assert result[0]["bucket"]["id"] == 2 + assert result[1]["creator"]["name"] == "Annie Bryan" + + @respx.mock + def test_checkins_feed_embeds_bucket_roots(self): + route = respx.get("https://3.basecampapi.com/12345/checkins.json").mock( + return_value=httpx.Response(200, json=_CHECKINS_FEED) + ) + + account = Client(access_token="test-token").for_account("12345") + result = account.everything.get_everything_checkins() + + assert route.called + assert len(result) == 2 + assert result[0]["id"] == 1200 + assert result[0]["type"] == "Question::Answer" + assert result[0]["bucket"]["type"] == "Project" + + @respx.mock + def test_forwards_feed_embeds_bucket_roots(self): + route = respx.get("https://3.basecampapi.com/12345/forwards.json").mock( + return_value=httpx.Response(200, json=_FORWARDS_FEED) + ) + + account = Client(access_token="test-token").for_account("12345") + result = account.everything.get_everything_forwards() + + assert route.called + assert len(result) == 2 + assert result[0]["id"] == 1300 + assert result[0]["type"] == "Inbox::Forward" + assert result[0]["subject"] == "FW: Invoice" + assert result[0]["bucket"]["id"] == 2 + + @respx.mock + def test_boosts_feed_wraps_nested_recording(self): + route = respx.get("https://3.basecampapi.com/12345/boosts.json").mock( + return_value=httpx.Response(200, json=_BOOSTS_FEED) + ) + + account = Client(access_token="test-token").for_account("12345") + result = account.everything.get_everything_boosts() + + assert route.called + assert len(result) == 2 + assert result[0]["id"] == 1400 + assert result[0]["content"] == "👍" + assert result[0]["recording"] == {"id": 1000, "type": "Message", "title": "Kickoff"} + assert result[1]["recording"]["type"] == "Comment" + + +class TestEverythingOverdueFeeds: + @respx.mock + def test_overdue_todos_returns_oldest_first_bare_array(self): + route = respx.get("https://3.basecampapi.com/12345/todos/overdue.json").mock( + return_value=httpx.Response(200, json=_OVERDUE_TODOS_FEED) + ) + + account = Client(access_token="test-token").for_account("12345") + result = account.everything.get_everything_overdue_todos() + + assert route.called + assert len(result) == 2 + assert result[0]["id"] == 1500 + assert result[0]["type"] == "Todo" + assert result[0]["due_on"] == "2024-01-01" + assert result[0]["due_on"] < result[1]["due_on"] + + @respx.mock + def test_overdue_cards_returns_oldest_first_bare_array(self): + route = respx.get("https://3.basecampapi.com/12345/cards/overdue.json").mock( + return_value=httpx.Response(200, json=_OVERDUE_CARDS_FEED) + ) + + account = Client(access_token="test-token").for_account("12345") + result = account.everything.get_everything_overdue_cards() + + assert route.called + assert len(result) == 2 + assert result[0]["id"] == 1600 + assert result[0]["type"] == "Kanban::Card" + assert result[0]["due_on"] == "2024-02-01" + assert result[0]["due_on"] < result[1]["due_on"] diff --git a/ruby/lib/basecamp/client.rb b/ruby/lib/basecamp/client.rb index de059723b..231f15ab1 100644 --- a/ruby/lib/basecamp/client.rb +++ b/ruby/lib/basecamp/client.rb @@ -501,6 +501,11 @@ def timeline service(:timeline) { Services::TimelineService.new(self) } end + # @return [Services::EverythingService] + def everything + service(:everything) { Services::EverythingService.new(self) } + end + # @return [Services::TimesheetsService] def timesheets service(:timesheets) { Services::TimesheetsService.new(self) } diff --git a/ruby/lib/basecamp/generated/metadata.json b/ruby/lib/basecamp/generated/metadata.json index 2dde59696..25a5aa698 100644 --- a/ruby/lib/basecamp/generated/metadata.json +++ b/ruby/lib/basecamp/generated/metadata.json @@ -1,7 +1,7 @@ { "$schema": "https://basecamp.com/schemas/sdk-metadata.json", "version": "1.0.0", - "generated": "2026-07-25T17:41:28Z", + "generated": "2026-07-25T17:48:06Z", "operations": { "GetAccount": { "retry": { @@ -67,6 +67,22 @@ ] } }, + "GetEverythingBoosts": { + "retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + }, + "pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + } + }, "GetBoost": { "retry": { "maxAttempts": 3, @@ -418,6 +434,17 @@ ] } }, + "GetEverythingOverdueCards": { + "retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, "ListMessageTypes": { "retry": { "maxAttempts": 3, @@ -670,6 +697,22 @@ ] } }, + "GetEverythingCheckins": { + "retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + }, + "pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + } + }, "ListPingablePeople": { "retry": { "maxAttempts": 3, @@ -767,6 +810,22 @@ ] } }, + "GetEverythingComments": { + "retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + }, + "pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + } + }, "GetComment": { "retry": { "maxAttempts": 3, @@ -856,6 +915,38 @@ "natural": true } }, + "GetEverythingFiles": { + "retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + }, + "pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + } + }, + "GetEverythingForwards": { + "retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + }, + "pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + } + }, "GetGaugeNeedle": { "retry": { "maxAttempts": 3, @@ -1059,6 +1150,22 @@ ] } }, + "GetEverythingMessages": { + "retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + }, + "pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + } + }, "GetMessage": { "retry": { "maxAttempts": 3, @@ -2387,6 +2494,17 @@ ] } }, + "GetEverythingOverdueTodos": { + "retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, "GetTodo": { "retry": { "maxAttempts": 3, diff --git a/ruby/lib/basecamp/generated/services/everything_service.rb b/ruby/lib/basecamp/generated/services/everything_service.rb new file mode 100644 index 000000000..9edb61eaa --- /dev/null +++ b/ruby/lib/basecamp/generated/services/everything_service.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +module Basecamp + module Services + # Service for Everything operations + # + # @generated from OpenAPI spec + class EverythingService < BaseService + + # Get every boost across all accessible projects, newest-first (paginated). + # @param page [Integer, nil] Page number for paginating through results. Defaults to 1. + # @return [Enumerator] paginated results + def get_everything_boosts(page: nil) + wrap_paginated(service: "everything", operation: "get_everything_boosts", is_mutation: false) do + params = compact_query_params(page: page) + paginate("/boosts.json", params: params) + end + end + + # Get every overdue card across all accessible projects, oldest-due-date-first. + # @return [Hash] response data + def get_everything_overdue_cards() + with_operation(service: "everything", operation: "get_everything_overdue_cards", is_mutation: false) do + http_get("/cards/overdue.json").json + end + end + + # Get every automatic check-in answer across all accessible projects, + # @param page [Integer, nil] Page number for paginating through results. Defaults to 1. + # @return [Enumerator] paginated results + def get_everything_checkins(page: nil) + wrap_paginated(service: "everything", operation: "get_everything_checkins", is_mutation: false) do + params = compact_query_params(page: page) + paginate("/checkins.json", params: params) + end + end + + # Get every comment across all accessible projects, newest-first (paginated). + # @param page [Integer, nil] Page number for paginating through results. Defaults to 1. + # @return [Enumerator] paginated results + def get_everything_comments(page: nil) + wrap_paginated(service: "everything", operation: "get_everything_comments", is_mutation: false) do + params = compact_query_params(page: page) + paginate("/comments.json", params: params) + end + end + + # Get every file recording across all accessible projects, newest-first + # @param kind [String, nil] Filter by file kind: all (default), images, pdfs, documents, or videos. + # @param people_ids [Array, nil] Restrict to files created by the given people (repeatable). + # @param page [Integer, nil] Page number for paginating through results. Defaults to 1. + # @return [Enumerator] paginated results + def get_everything_files(kind: nil, people_ids: nil, page: nil) + wrap_paginated(service: "everything", operation: "get_everything_files", is_mutation: false) do + params = compact_query_params(kind: kind, people_ids: people_ids, page: page) + paginate("/files.json", params: params) + end + end + + # Get every inbox forward across all accessible projects, newest-first + # @param page [Integer, nil] Page number for paginating through results. Defaults to 1. + # @return [Enumerator] paginated results + def get_everything_forwards(page: nil) + wrap_paginated(service: "everything", operation: "get_everything_forwards", is_mutation: false) do + params = compact_query_params(page: page) + paginate("/forwards.json", params: params) + end + end + + # Get every message across all accessible projects, newest-first (paginated). + # @param page [Integer, nil] Page number for paginating through results. Defaults to 1. + # @return [Enumerator] paginated results + def get_everything_messages(page: nil) + wrap_paginated(service: "everything", operation: "get_everything_messages", is_mutation: false) do + params = compact_query_params(page: page) + paginate("/messages.json", params: params) + end + end + + # Get every overdue to-do across all accessible projects, oldest-due-date-first. + # @return [Hash] response data + def get_everything_overdue_todos() + with_operation(service: "everything", operation: "get_everything_overdue_todos", is_mutation: false) do + http_get("/todos/overdue.json").json + end + end + end + end +end diff --git a/ruby/lib/basecamp/generated/types.rb b/ruby/lib/basecamp/generated/types.rb index 8cb12e447..f63de3956 100644 --- a/ruby/lib/basecamp/generated/types.rb +++ b/ruby/lib/basecamp/generated/types.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Auto-generated from OpenAPI spec. Do not edit manually. -# Generated: 2026-07-25T17:41:28Z +# Generated: 2026-07-25T17:48:06Z require "json" require "time" @@ -1360,6 +1360,84 @@ def to_json(*args) end end + # EverythingFile + class EverythingFile + include TypeHelpers + attr_accessor :app_download_url, :app_url, :attachable_sgid, :bookmark_url, :boosts_count, :boosts_url, :bucket, :byte_size, :comments_count, :comments_url, :content_type, :created_at, :creator, :description, :description_attachments, :download_url, :filename, :height, :id, :inherits_status, :parent, :position, :status, :subscription_url, :title, :type, :updated_at, :url, :visible_to_clients, :width + + def initialize(data = {}) + @app_download_url = data["app_download_url"] + @app_url = data["app_url"] + @attachable_sgid = data["attachable_sgid"] + @bookmark_url = data["bookmark_url"] + @boosts_count = parse_integer(data["boosts_count"]) + @boosts_url = data["boosts_url"] + @bucket = parse_type(data["bucket"], "RecordingBucket") + @byte_size = parse_integer(data["byte_size"]) + @comments_count = parse_integer(data["comments_count"]) + @comments_url = data["comments_url"] + @content_type = data["content_type"] + @created_at = parse_datetime(data["created_at"]) + @creator = parse_type(data["creator"], "Person") + @description = data["description"] + @description_attachments = parse_array(data["description_attachments"], "RichTextAttachment") + @download_url = data["download_url"] + @filename = data["filename"] + @height = parse_integer(data["height"]) + @id = parse_integer(data["id"]) + @inherits_status = parse_boolean(data["inherits_status"]) + @parent = parse_type(data["parent"], "RecordingParent") + @position = parse_integer(data["position"]) + @status = data["status"] + @subscription_url = data["subscription_url"] + @title = data["title"] + @type = data["type"] + @updated_at = parse_datetime(data["updated_at"]) + @url = data["url"] + @visible_to_clients = parse_boolean(data["visible_to_clients"]) + @width = parse_integer(data["width"]) + end + + def to_h + { + "app_download_url" => @app_download_url, + "app_url" => @app_url, + "attachable_sgid" => @attachable_sgid, + "bookmark_url" => @bookmark_url, + "boosts_count" => @boosts_count, + "boosts_url" => @boosts_url, + "bucket" => @bucket, + "byte_size" => @byte_size, + "comments_count" => @comments_count, + "comments_url" => @comments_url, + "content_type" => @content_type, + "created_at" => @created_at, + "creator" => @creator, + "description" => @description, + "description_attachments" => @description_attachments, + "download_url" => @download_url, + "filename" => @filename, + "height" => @height, + "id" => @id, + "inherits_status" => @inherits_status, + "parent" => @parent, + "position" => @position, + "status" => @status, + "subscription_url" => @subscription_url, + "title" => @title, + "type" => @type, + "updated_at" => @updated_at, + "url" => @url, + "visible_to_clients" => @visible_to_clients, + "width" => @width, + }.compact + end + + def to_json(*args) + to_h.to_json(*args) + end + end + # Forward class Forward include TypeHelpers diff --git a/ruby/test/basecamp/services/everything_service_test.rb b/ruby/test/basecamp/services/everything_service_test.rb new file mode 100644 index 000000000..794cc702e --- /dev/null +++ b/ruby/test/basecamp/services/everything_service_test.rb @@ -0,0 +1,326 @@ +# frozen_string_literal: true + +# Tests for the EverythingService (generated from OpenAPI spec) +# +# Note: Generated services are spec-conformant: +# - get_everything_files() is paginated and returns the parsed JSON array +# - No client-side validation (API validates) + +require "test_helper" + +class EverythingServiceTest < Minitest::Test + include TestHelper + + def setup + @account = create_account_client(account_id: "12345") + end + + def test_get_everything_files_decodes_heterogeneous_feed + files = [ + { + "id" => 900, + "type" => "Upload", + "status" => "active", + "visible_to_clients" => false, + "title" => "logo.png", + "inherits_status" => true, + "filename" => "logo.png", + "content_type" => "image/png", + "byte_size" => 1281, + "width" => 1024.0, + "height" => 768.0, + "url" => "https://3.basecampapi.com/1/buckets/2/uploads/900.json", + "app_url" => "https://3.basecamp.com/1/buckets/2/uploads/900", + "download_url" => "https://3.basecampapi.com/1/buckets/2/uploads/900/download/logo.png", + "app_download_url" => "https://storage.3.basecamp.com/1/buckets/2/uploads/900/download/logo.png", + "bucket" => { "id" => 2, "name" => "The Leto Laptop", "type" => "Project" }, + "creator" => { "id" => 1, "name" => "Victor Cooper" } + }, + { + "id" => 901, + "type" => "Document", + "status" => "active", + "visible_to_clients" => false, + "title" => "Spec", + "inherits_status" => true, + "content_type" => "text/html", + "url" => "https://3.basecampapi.com/1/buckets/2/documents/901.json", + "app_url" => "https://3.basecamp.com/1/buckets/2/documents/901", + "bucket" => { "id" => 2, "name" => "The Leto Laptop", "type" => "Project" }, + "creator" => { "id" => 1, "name" => "Victor Cooper" } + }, + { + "id" => 902, + "type" => "Attachment", + "attachable_sgid" => "sgid-902", + "filename" => "chart.avif", + "content_type" => "image/avif", + "byte_size" => 4096, + "width" => nil, + "height" => nil, + "download_url" => "https://storage.3.basecamp.com/1/blobs/902/download/chart.avif", + "parent" => { "id" => 800, "title" => "A message", "type" => "Message" } + } + ] + + stub_get("/12345/files.json", response_body: files) + + result = @account.everything.get_everything_files(kind: nil, people_ids: nil).to_a + + assert_kind_of Array, result + assert_equal 3, result.length + + # Variant 0: full Upload recording + assert_equal "Upload", result[0]["type"] + assert_equal "logo.png", result[0]["filename"] + assert_not_nil result[0]["app_download_url"] + assert_equal 1024.0, result[0]["width"] + + # Variant 1: Basecamp Document recording + assert_equal "Document", result[1]["type"] + assert_equal "Spec", result[1]["title"] + + # Variant 2: rich-text Attachment envelope + assert_equal "Attachment", result[2]["type"] + assert_equal "sgid-902", result[2]["attachable_sgid"] + assert_not_nil result[2]["parent"] + assert_nil result[2]["width"] + end + + def test_get_everything_messages_decodes_recording_feed + messages = [ + { + "id" => 1001, + "type" => "Message", + "status" => "active", + "visible_to_clients" => false, + "title" => "Kickoff", + "bucket" => { "id" => 2, "name" => "The Leto Laptop", "type" => "Project" }, + "creator" => { "id" => 1, "name" => "Victor Cooper" } + }, + { + "id" => 1002, + "type" => "Message", + "status" => "active", + "visible_to_clients" => true, + "title" => "Update", + "bucket" => { "id" => 3, "name" => "Honcho Design", "type" => "Project" }, + "creator" => { "id" => 1, "name" => "Victor Cooper" } + } + ] + + stub_get("/12345/messages.json", response_body: messages) + + result = @account.everything.get_everything_messages.to_a + + assert_kind_of Array, result + assert_equal 2, result.length + assert_equal 1001, result[0]["id"] + assert_equal "Message", result[0]["type"] + assert_equal 2, result[0]["bucket"]["id"] + assert_equal "Project", result[0]["bucket"]["type"] + end + + def test_get_everything_comments_decodes_recording_feed + comments = [ + { + "id" => 2001, + "type" => "Comment", + "status" => "active", + "visible_to_clients" => false, + "bucket" => { "id" => 2, "name" => "The Leto Laptop", "type" => "Project" }, + "creator" => { "id" => 1, "name" => "Victor Cooper" } + }, + { + "id" => 2002, + "type" => "Comment", + "status" => "active", + "visible_to_clients" => false, + "bucket" => { "id" => 3, "name" => "Honcho Design", "type" => "Project" }, + "creator" => { "id" => 1, "name" => "Victor Cooper" } + } + ] + + stub_get("/12345/comments.json", response_body: comments) + + result = @account.everything.get_everything_comments.to_a + + assert_kind_of Array, result + assert_equal 2, result.length + assert_equal 2001, result[0]["id"] + assert_equal "Comment", result[0]["type"] + assert_equal 2, result[0]["bucket"]["id"] + end + + def test_get_everything_checkins_decodes_recording_feed + checkins = [ + { + "id" => 3001, + "type" => "Question::Answer", + "status" => "active", + "visible_to_clients" => false, + "bucket" => { "id" => 2, "name" => "The Leto Laptop", "type" => "Project" }, + "creator" => { "id" => 1, "name" => "Victor Cooper" } + }, + { + "id" => 3002, + "type" => "Question::Answer", + "status" => "active", + "visible_to_clients" => false, + "bucket" => { "id" => 3, "name" => "Honcho Design", "type" => "Project" }, + "creator" => { "id" => 1, "name" => "Victor Cooper" } + } + ] + + stub_get("/12345/checkins.json", response_body: checkins) + + result = @account.everything.get_everything_checkins.to_a + + assert_kind_of Array, result + assert_equal 2, result.length + assert_equal 3001, result[0]["id"] + assert_equal "Question::Answer", result[0]["type"] + assert_equal 2, result[0]["bucket"]["id"] + end + + def test_get_everything_forwards_decodes_recording_feed + forwards = [ + { + "id" => 4001, + "type" => "Inbox::Forward", + "status" => "active", + "visible_to_clients" => false, + "subject" => "FW: Invoice", + "bucket" => { "id" => 2, "name" => "The Leto Laptop", "type" => "Project" }, + "creator" => { "id" => 1, "name" => "Victor Cooper" } + }, + { + "id" => 4002, + "type" => "Inbox::Forward", + "status" => "active", + "visible_to_clients" => false, + "subject" => "FW: Contract", + "bucket" => { "id" => 3, "name" => "Honcho Design", "type" => "Project" }, + "creator" => { "id" => 1, "name" => "Victor Cooper" } + } + ] + + stub_get("/12345/forwards.json", response_body: forwards) + + result = @account.everything.get_everything_forwards.to_a + + assert_kind_of Array, result + assert_equal 2, result.length + assert_equal 4001, result[0]["id"] + assert_equal "Inbox::Forward", result[0]["type"] + assert_equal 2, result[0]["bucket"]["id"] + end + + def test_get_everything_boosts_decodes_boost_feed + boosts = [ + { + "id" => 5001, + "content" => "🎉", + "created_at" => "2024-01-15T10:00:00Z", + "creator" => { "id" => 1, "name" => "Victor Cooper" }, + "recording" => { + "id" => 1001, + "type" => "Message", + "title" => "Kickoff", + "bucket" => { "id" => 2, "name" => "The Leto Laptop", "type" => "Project" } + } + }, + { + "id" => 5002, + "content" => "👏", + "created_at" => "2024-01-15T09:00:00Z", + "creator" => { "id" => 1, "name" => "Victor Cooper" }, + "recording" => { + "id" => 2001, + "type" => "Comment", + "bucket" => { "id" => 3, "name" => "Honcho Design", "type" => "Project" } + } + } + ] + + stub_get("/12345/boosts.json", response_body: boosts) + + result = @account.everything.get_everything_boosts.to_a + + assert_kind_of Array, result + assert_equal 2, result.length + assert_equal 5001, result[0]["id"] + assert_equal "🎉", result[0]["content"] + assert_not_nil result[0]["recording"] + assert_equal 1001, result[0]["recording"]["id"] + assert_equal "Message", result[0]["recording"]["type"] + end + + def test_get_everything_overdue_todos_decodes_oldest_first + todos = [ + { + "id" => 6001, + "type" => "Todo", + "status" => "active", + "visible_to_clients" => false, + "title" => "Ship the thing", + "due_on" => "2024-01-01", + "bucket" => { "id" => 2, "name" => "The Leto Laptop", "type" => "Project" } + }, + { + "id" => 6002, + "type" => "Todo", + "status" => "active", + "visible_to_clients" => false, + "title" => "Review the doc", + "due_on" => "2024-02-01", + "bucket" => { "id" => 3, "name" => "Honcho Design", "type" => "Project" } + } + ] + + stub_get("/12345/todos/overdue.json", response_body: todos) + + result = @account.everything.get_everything_overdue_todos.to_a + + assert_kind_of Array, result + assert_equal 2, result.length + assert_equal 6001, result[0]["id"] + assert_equal "2024-01-01", result[0]["due_on"] + # Oldest-first ordering + assert result[0]["due_on"] < result[1]["due_on"] + end + + def test_get_everything_overdue_cards_decodes_oldest_first + cards = [ + { + "id" => 7001, + "type" => "Kanban::Card", + "status" => "active", + "visible_to_clients" => false, + "title" => "Draft proposal", + "due_on" => "2024-01-10", + "bucket" => { "id" => 2, "name" => "The Leto Laptop", "type" => "Project" } + }, + { + "id" => 7002, + "type" => "Kanban::Card", + "status" => "active", + "visible_to_clients" => false, + "title" => "Send invoice", + "due_on" => "2024-02-10", + "bucket" => { "id" => 3, "name" => "Honcho Design", "type" => "Project" } + } + ] + + stub_get("/12345/cards/overdue.json", response_body: cards) + + result = @account.everything.get_everything_overdue_cards.to_a + + assert_kind_of Array, result + assert_equal 2, result.length + assert_equal 7001, result[0]["id"] + assert_equal "2024-01-10", result[0]["due_on"] + # Oldest-first ordering + assert result[0]["due_on"] < result[1]["due_on"] + end +end diff --git a/scripts/enhance-openapi-go-types.sh b/scripts/enhance-openapi-go-types.sh index 09b316af1..24e94395d 100755 --- a/scripts/enhance-openapi-go-types.sh +++ b/scripts/enhance-openapi-go-types.sh @@ -226,6 +226,38 @@ walk( (.previewable // empty) += { "x-go-type-skip-optional-pointer": false } ) | +# Fifth-d pass: EverythingFile width/height → nullable *types.FlexInt +# The /files.json feed mixes uploads and attachments whose pixel dimensions are +# float-spelled (1024.0) and null for non-image blobs, exactly like +# RichTextAttachment/TimelineAttachment. Keep the optional pointer and mark +# nullable so the present-null value types faithfully. +.components.schemas.EverythingFile.properties |= ( + (.width // empty) += { + "nullable": true, + "x-go-type": "types.FlexInt", + "x-go-type-import": {"path": "github.com/basecamp/basecamp-sdk/go/pkg/types"}, + "x-go-type-skip-optional-pointer": false + } | + (.height // empty) += { + "nullable": true, + "x-go-type": "types.FlexInt", + "x-go-type-import": {"path": "github.com/basecamp/basecamp-sdk/go/pkg/types"}, + "x-go-type-skip-optional-pointer": false + } +) +| +# Fifth-f pass: EverythingFile presence-faithful optional scalars (same rationale +# as TimelineAttachment). The /files.json superset populates only one variant per +# instance, so its optional timestamps and booleans must round-trip presence: +# make created_at/updated_at *time.Time and visible_to_clients/inherits_status +# *bool so nil (absent variant) omits and an explicit false is preserved. +.components.schemas.EverythingFile.properties |= ( + (.created_at // empty) += { "x-go-type-skip-optional-pointer": false } | + (.updated_at // empty) += { "x-go-type-skip-optional-pointer": false } | + (.visible_to_clients // empty) += { "x-go-type-skip-optional-pointer": false } | + (.inherits_status // empty) += { "x-go-type-skip-optional-pointer": false } +) +| # Sixth pass: Person.id → types.FlexibleInt64 # The API sometimes returns person IDs as JSON strings (e.g. in notification # responses); Go rejects those into int64 fields. Scoped to Person schema only. diff --git a/spec/api-gaps/README.md b/spec/api-gaps/README.md index a1a2c884d..cf5bae440 100644 --- a/spec/api-gaps/README.md +++ b/spec/api-gaps/README.md @@ -39,7 +39,7 @@ making the absorption journey publicly auditable. | [calendar](calendar.md) | addressed-in-bc3-pr-12321 | 3b | medium | | [scratchpad](scratchpad.md) | addressed-in-bc3-pr-12322 | 3b | medium | | [step-top-level](step-top-level.md) | absorbed-in-sdk | 3b | low | -| [everything-aggregates](everything-aggregates.md) | addressed-in-bc3-pr-11627 | 3c | high | +| [everything-aggregates](everything-aggregates.md) | partial-coverage | 3c | high | | [activity-timeline](activity-timeline.md) | absorbed-in-sdk | 3d | high | | [recordable-subtypes-doc](recordable-subtypes-doc.md) | partial-coverage | 3a | medium | | [stack-doc-and-smithy](stack-doc-and-smithy.md) | confirmed-not-api-resource | 3b | medium | diff --git a/spec/api-gaps/everything-aggregates.md b/spec/api-gaps/everything-aggregates.md index 419d1e1ba..90576df01 100644 --- a/spec/api-gaps/everything-aggregates.md +++ b/spec/api-gaps/everything-aggregates.md @@ -1,9 +1,12 @@ --- gap: everything-aggregates -status: addressed-in-bc3-pr-11627 +status: partial-coverage detected: 2026-05-01 sdk_demand: high bc3_pr: 11627 +smithy_refs: + - "EverythingService flat family: GetEverythingMessages/Comments/Checkins/Forwards/Boosts/Files + GetEverythingOverdueTodos/OverdueCards (spec/basecamp.smithy)" + - "EverythingFile superset (spec/basecamp.smithy)" bc3_refs: introduced_in: five bc3_plan_phase: 3c @@ -112,16 +115,58 @@ merged doc. ## SDK absorption plan when this lands -- New `EverythingService` with **17** operations matching the merged contract - (one per endpoint in the frontmatter route list). Re-verify the operation - list against `doc/api/sections/everything.md` at absorption time. -- Two response families: a bucket-grouped shape for the todo/card filter - sub-routes (bucket + grouped recordings + steps) and flat recording arrays - for the overdue lists and the 6 roots. Reuse existing recording shapes - (`Todo`, `Card`, `Message`, etc.) for the elements. -- Do **not** model the `//recent.json` aliases — internal web feeds, - not API contract. Do not model bare `/todos.json` / `/cards.json` (HTML - shells). -- Model the `/files.json` `kind` and `people_ids[]` query filters. -- Canary fixture: cover at least one operation per group (8 groups) to catch - shape drift. Pairwise check: BC4 absent → BC5 present is fine. +Absorbed in **two phases** across a stacked PR pair. This entry stays +`partial-coverage` until **both** phases land; it flips to `absorbed-in-sdk` +only when all 17 routes are modeled. + +**PR-5 (flat family) — DONE.** A new `EverythingService` with the 8 flat-family +operations: + +- Six recency-ordered, Link-paginated roots — `GetEverythingMessages`, + `GetEverythingComments`, `GetEverythingCheckins`, `GetEverythingForwards` + (element = the generic `Recording` projection the wire actually returns, which + embeds `bucket`), `GetEverythingBoosts` (element = `Boost`, carrying its + `booster` and nested `recording`), and `GetEverythingFiles`. +- Two unpaginated, oldest-due-date-first arrays — `GetEverythingOverdueTodos` + (`Todo`) and `GetEverythingOverdueCards` (`Card`) — modeled as plain full + arrays (single-member output, no pagination → bare array via the + `smithy-bare-arrays` transform), tested as complete oldest-first with **no** + Link-following. +- `/files.json` is **heterogeneous** (Upload + Document + attachment-envelope); + modeled as the optional-field superset `EverythingFile` (the cross-cutting + untagged-polymorphism default), with the `kind` and repeatable `people_ids[]` + query filters. **Runtime decode proof:** every SDK (Go, TS, Ruby, Python, + Kotlin, Swift) has a non-empty test decoding all three variants in one array. +- The generator auto-derives the `EverythingService` from the `Everything` tag; + client wiring added where hand-written (Go accessor, TS `defineService` **plus + package-root re-export**, Ruby `def everything`, Python sync+async properties; + Kotlin/Swift auto-wired). `EverythingFile`/`Boost` added to the TS and Kotlin + generator type registries so the paginated methods advertise `ListResult` + rather than a raw array alias; the generator now resolves `$ref` array aliases + so the unpaginated overdue methods no longer mis-advertise `dict`/`Hash`. +- Each paginated flat op carries a `page` `@httpQuery` param, forwarded by the + Go wrapper (a positive `page` fetches that page; `page 0` follows the Link + header) — previously the Go `page` argument was silently ignored. +- `EverythingFile` optional timestamps/booleans are pointer-preserving (Go) so + re-marshaling the superset omits absent-variant fields instead of fabricating + a zero timestamp or dropping an explicit `false`. +- Go wrappers with multi-page Link-following (paginated roots) and plain + full-array decode (overdue); the flat-aggregate mirror of `GetMyAssignments`. +- **Tests:** Go multi-page/overdue/files/page-forwarding tests, plus happy-path + per-op tests for all 8 flat ops in TS/Ruby/Python and per-variant `/files.json` + decode in all six SDKs. +- **Conformance/canary:** `paths.json` path-assertion entries + mock-runner + dispatch (Go/TS/Kotlin) for all 8 flat ops; a live-canary entry per group in + `live-my-surface.json` with matching `live-dispatch` cases (live canary + dormant → validates statically). + +**PR-5b (bucket-grouped family) — PENDING.** The 9 todo/card filter sub-routes +(`/todos/{open,completed,unassigned,no_due_date}.json`, +`/cards/{open,completed,unassigned,no_due_date,not_now}.json`) return a +paginated array of `{bucket, todos|cards}` with steps. Modeled + covered +(tests, page params, conformance, canary) in PR-5b, after which this entry +flips to `absorbed-in-sdk`. + +Exclusions honored: never model the `//recent.json` aliases (internal +web feeds) or the bare `/todos.json` / `/cards.json` roots (HTML shells). +Pairwise check: BC4 absent → BC5 present is fine. diff --git a/spec/basecamp.smithy b/spec/basecamp.smithy index cd370f683..0af4a671e 100644 --- a/spec/basecamp.smithy +++ b/spec/basecamp.smithy @@ -289,6 +289,16 @@ service Basecamp { GetMyCompletedAssignments, GetMyDueAssignments, + // Batch 15b - Everything Aggregates (flat family) + GetEverythingMessages, + GetEverythingComments, + GetEverythingCheckins, + GetEverythingForwards, + GetEverythingBoosts, + GetEverythingFiles, + GetEverythingOverdueTodos, + GetEverythingOverdueCards, + // Batch 16 - My Notifications GetMyNotifications, GetBubbleUps, @@ -8682,6 +8692,287 @@ structure GetMyDueAssignmentsOutput { assignments: MyAssignmentList } +// ============================================================================= +// BATCH 15b - Everything Aggregates (flat family) +// ============================================================================= +// +// Account-wide recording listings served by the everything/*_controller.rb +// namespace under flat top-level paths (the /everything/... segment is the +// Rails controller namespace, not part of the URL). Documented by BC3 #11627 in +// doc/api/sections/everything.md. This is the flat family: six recency-ordered, +// Link-paginated roots plus two unpaginated oldest-first overdue lists. The +// bucket-grouped todo/card filter sub-routes are a separate family. Never model +// the bare /todos.json or /cards.json roots (HTML shells) or the internal +// //recent.json feeds. + +/// Get every message across all accessible projects, newest-first (paginated). +/// Each item embeds its `bucket` for project context. +@readonly +@basecampRetry(maxAttempts: 3, baseDelayMs: 1000, backoff: "exponential", retryOn: [429, 503]) +@basecampPagination(style: "link", totalCountHeader: "X-Total-Count", maxPageSize: 50) +@http(method: "GET", uri: "/{accountId}/messages.json") +operation GetEverythingMessages { + input: GetEverythingMessagesInput + output: GetEverythingMessagesOutput + errors: [UnauthorizedError, ForbiddenError, RateLimitError, InternalServerError] +} + +structure GetEverythingMessagesInput { + @required + @httpLabel + accountId: AccountId + + /// Page number for paginating through results. Defaults to 1. + @httpQuery("page") + page: Integer +} + +structure GetEverythingMessagesOutput { + recordings: RecordingList +} + +/// Get every comment across all accessible projects, newest-first (paginated). +/// Each item embeds its `bucket`. +@readonly +@basecampRetry(maxAttempts: 3, baseDelayMs: 1000, backoff: "exponential", retryOn: [429, 503]) +@basecampPagination(style: "link", totalCountHeader: "X-Total-Count", maxPageSize: 50) +@http(method: "GET", uri: "/{accountId}/comments.json") +operation GetEverythingComments { + input: GetEverythingCommentsInput + output: GetEverythingCommentsOutput + errors: [UnauthorizedError, ForbiddenError, RateLimitError, InternalServerError] +} + +structure GetEverythingCommentsInput { + @required + @httpLabel + accountId: AccountId + + /// Page number for paginating through results. Defaults to 1. + @httpQuery("page") + page: Integer +} + +structure GetEverythingCommentsOutput { + recordings: RecordingList +} + +/// Get every automatic check-in answer across all accessible projects, +/// newest-first (paginated). Each item embeds its `bucket`. +@readonly +@basecampRetry(maxAttempts: 3, baseDelayMs: 1000, backoff: "exponential", retryOn: [429, 503]) +@basecampPagination(style: "link", totalCountHeader: "X-Total-Count", maxPageSize: 50) +@http(method: "GET", uri: "/{accountId}/checkins.json") +operation GetEverythingCheckins { + input: GetEverythingCheckinsInput + output: GetEverythingCheckinsOutput + errors: [UnauthorizedError, ForbiddenError, RateLimitError, InternalServerError] +} + +structure GetEverythingCheckinsInput { + @required + @httpLabel + accountId: AccountId + + /// Page number for paginating through results. Defaults to 1. + @httpQuery("page") + page: Integer +} + +structure GetEverythingCheckinsOutput { + recordings: RecordingList +} + +/// Get every inbox forward across all accessible projects, newest-first +/// (paginated). Each item embeds its `bucket`. +@readonly +@basecampRetry(maxAttempts: 3, baseDelayMs: 1000, backoff: "exponential", retryOn: [429, 503]) +@basecampPagination(style: "link", totalCountHeader: "X-Total-Count", maxPageSize: 50) +@http(method: "GET", uri: "/{accountId}/forwards.json") +operation GetEverythingForwards { + input: GetEverythingForwardsInput + output: GetEverythingForwardsOutput + errors: [UnauthorizedError, ForbiddenError, RateLimitError, InternalServerError] +} + +structure GetEverythingForwardsInput { + @required + @httpLabel + accountId: AccountId + + /// Page number for paginating through results. Defaults to 1. + @httpQuery("page") + page: Integer +} + +structure GetEverythingForwardsOutput { + recordings: RecordingList +} + +/// Get every boost across all accessible projects, newest-first (paginated). +/// Each boost carries its `booster` and the `recording` it boosts. +@readonly +@basecampRetry(maxAttempts: 3, baseDelayMs: 1000, backoff: "exponential", retryOn: [429, 503]) +@basecampPagination(style: "link", totalCountHeader: "X-Total-Count", maxPageSize: 50) +@http(method: "GET", uri: "/{accountId}/boosts.json") +operation GetEverythingBoosts { + input: GetEverythingBoostsInput + output: GetEverythingBoostsOutput + errors: [UnauthorizedError, ForbiddenError, RateLimitError, InternalServerError] +} + +structure GetEverythingBoostsInput { + @required + @httpLabel + accountId: AccountId + + /// Page number for paginating through results. Defaults to 1. + @httpQuery("page") + page: Integer +} + +structure GetEverythingBoostsOutput { + boosts: BoostList +} + +/// Get every file recording across all accessible projects, newest-first +/// (paginated). Heterogeneous: uploads and Basecamp documents carry their +/// standard recording shapes, while rich-text attachments are wrapped in a +/// recording envelope plus an `attachable_sgid` and blob metadata. Modeled as +/// an optional-field superset (EverythingFile) so one element type decodes any +/// variant. +@readonly +@basecampRetry(maxAttempts: 3, baseDelayMs: 1000, backoff: "exponential", retryOn: [429, 503]) +@basecampPagination(style: "link", totalCountHeader: "X-Total-Count", maxPageSize: 50) +@http(method: "GET", uri: "/{accountId}/files.json") +operation GetEverythingFiles { + input: GetEverythingFilesInput + output: GetEverythingFilesOutput + errors: [UnauthorizedError, ForbiddenError, RateLimitError, InternalServerError] +} + +structure GetEverythingFilesInput { + @required + @httpLabel + accountId: AccountId + + /// Filter by file kind: all (default), images, pdfs, documents, or videos. + @httpQuery("kind") + kind: String + + /// Restrict to files created by the given people (repeatable). + @httpQuery("people_ids[]") + people_ids: PersonIdList + + /// Page number for paginating through results. Defaults to 1. + @httpQuery("page") + page: Integer +} + +structure GetEverythingFilesOutput { + files: EverythingFileList +} + +/// Get every overdue to-do across all accessible projects, oldest-due-date-first. +/// A complete, unpaginated array; each item embeds its `bucket`. +@readonly +@basecampRetry(maxAttempts: 3, baseDelayMs: 1000, backoff: "exponential", retryOn: [429, 503]) +@http(method: "GET", uri: "/{accountId}/todos/overdue.json") +operation GetEverythingOverdueTodos { + input: GetEverythingOverdueTodosInput + output: GetEverythingOverdueTodosOutput + errors: [UnauthorizedError, ForbiddenError, InternalServerError] +} + +structure GetEverythingOverdueTodosInput { + @required + @httpLabel + accountId: AccountId +} + +structure GetEverythingOverdueTodosOutput { + todos: TodoItems +} + +/// Get every overdue card across all accessible projects, oldest-due-date-first. +/// A complete, unpaginated array; each item embeds its `bucket`. +@readonly +@basecampRetry(maxAttempts: 3, baseDelayMs: 1000, backoff: "exponential", retryOn: [429, 503]) +@http(method: "GET", uri: "/{accountId}/cards/overdue.json") +operation GetEverythingOverdueCards { + input: GetEverythingOverdueCardsInput + output: GetEverythingOverdueCardsOutput + errors: [UnauthorizedError, ForbiddenError, InternalServerError] +} + +structure GetEverythingOverdueCardsInput { + @required + @httpLabel + accountId: AccountId +} + +structure GetEverythingOverdueCardsOutput { + cards: CardList +} + +// ===== Everything File Shapes ===== + +list EverythingFileList { + member: EverythingFile +} + +/// A single item in the /files.json feed. An optional-field superset over three +/// wire variants — a full Upload recording, a Basecamp Document recording, and a +/// rich-text attachment wrapped in a recording envelope (distinguished by +/// `attachable_sgid` and blob metadata). Every field is optional; a given +/// instance populates only the fields of the variant it represents. Unknown +/// fields are ignored by every SDK decoder, so the superset need not enumerate +/// every field of the Upload/Document recordings. +structure EverythingFile { + /// Recording (Upload/Document) or attachment id. + id: Long + status: String + visible_to_clients: Boolean + created_at: ISO8601Timestamp + updated_at: ISO8601Timestamp + title: String + inherits_status: Boolean + /// "Upload", "Document", or "Attachment". + type: String + url: String + app_url: String + bookmark_url: String + subscription_url: String + comments_count: Integer + comments_url: String + boosts_count: Integer + boosts_url: String + position: Integer + parent: RecordingParent + bucket: RecordingBucket + creator: Person + + /// Present on the rich-text attachment variant: signed global id of the + /// attachment (uploads/documents omit it). + attachable_sgid: String + + // ----- blob/file metadata (uploads and attachments) ----- + content_type: String + byte_size: Long + filename: String + @basecampAuthRoutableUrl + download_url: String + app_download_url: String + /// Pixel width; null for non-image blobs and may be float-spelled (1024.0). + width: Integer + /// Pixel height; null for non-image blobs and may be float-spelled (1024.0). + height: Integer + + /// Rich-text description (upload/document variants). + description: String + description_attachments: RichTextAttachmentList +} + // ===== My Assignment Shapes ===== list MyAssignmentList { diff --git a/spec/overlays/tags.smithy b/spec/overlays/tags.smithy index 958079c46..b969c5ec1 100644 --- a/spec/overlays/tags.smithy +++ b/spec/overlays/tags.smithy @@ -219,6 +219,16 @@ apply GetMyAssignments @tags(["MyAssignments"]) apply GetMyCompletedAssignments @tags(["MyAssignments"]) apply GetMyDueAssignments @tags(["MyAssignments"]) +// Everything Aggregates (flat family) +apply GetEverythingMessages @tags(["Everything"]) +apply GetEverythingComments @tags(["Everything"]) +apply GetEverythingCheckins @tags(["Everything"]) +apply GetEverythingForwards @tags(["Everything"]) +apply GetEverythingBoosts @tags(["Everything"]) +apply GetEverythingFiles @tags(["Everything"]) +apply GetEverythingOverdueTodos @tags(["Everything"]) +apply GetEverythingOverdueCards @tags(["Everything"]) + // My Notifications apply GetMyNotifications @tags(["MyNotifications"]) apply GetBubbleUps @tags(["MyNotifications"]) diff --git a/swift/Sources/Basecamp/Generated/AccountClient+Services.swift b/swift/Sources/Basecamp/Generated/AccountClient+Services.swift index c3b5b72d8..6dc4f4ecd 100644 --- a/swift/Sources/Basecamp/Generated/AccountClient+Services.swift +++ b/swift/Sources/Basecamp/Generated/AccountClient+Services.swift @@ -19,6 +19,7 @@ extension AccountClient { public var comments: CommentsService { service("comments") { CommentsService(accountClient: self) } } public var documents: DocumentsService { service("documents") { DocumentsService(accountClient: self) } } public var events: EventsService { service("events") { EventsService(accountClient: self) } } + public var everything: EverythingService { service("everything") { EverythingService(accountClient: self) } } public var forwards: ForwardsService { service("forwards") { ForwardsService(accountClient: self) } } public var gauges: GaugesService { service("gauges") { GaugesService(accountClient: self) } } public var hillCharts: HillChartsService { service("hillCharts") { HillChartsService(accountClient: self) } } diff --git a/swift/Sources/Basecamp/Generated/Metadata.swift b/swift/Sources/Basecamp/Generated/Metadata.swift index 82a49ffa5..783d9bcb8 100644 --- a/swift/Sources/Basecamp/Generated/Metadata.swift +++ b/swift/Sources/Basecamp/Generated/Metadata.swift @@ -70,6 +70,14 @@ enum Metadata { "GetClientReply": RetryConfig(maxAttempts: 3, baseDelayMs: 1000, backoff: .exponential, retryOn: [429, 503]), "GetComment": RetryConfig(maxAttempts: 3, baseDelayMs: 1000, backoff: .exponential, retryOn: [429, 503]), "GetDocument": RetryConfig(maxAttempts: 3, baseDelayMs: 1000, backoff: .exponential, retryOn: [429, 503]), + "GetEverythingBoosts": RetryConfig(maxAttempts: 3, baseDelayMs: 1000, backoff: .exponential, retryOn: [429, 503]), + "GetEverythingCheckins": RetryConfig(maxAttempts: 3, baseDelayMs: 1000, backoff: .exponential, retryOn: [429, 503]), + "GetEverythingComments": RetryConfig(maxAttempts: 3, baseDelayMs: 1000, backoff: .exponential, retryOn: [429, 503]), + "GetEverythingFiles": RetryConfig(maxAttempts: 3, baseDelayMs: 1000, backoff: .exponential, retryOn: [429, 503]), + "GetEverythingForwards": RetryConfig(maxAttempts: 3, baseDelayMs: 1000, backoff: .exponential, retryOn: [429, 503]), + "GetEverythingMessages": RetryConfig(maxAttempts: 3, baseDelayMs: 1000, backoff: .exponential, retryOn: [429, 503]), + "GetEverythingOverdueCards": RetryConfig(maxAttempts: 3, baseDelayMs: 1000, backoff: .exponential, retryOn: [429, 503]), + "GetEverythingOverdueTodos": RetryConfig(maxAttempts: 3, baseDelayMs: 1000, backoff: .exponential, retryOn: [429, 503]), "GetForward": RetryConfig(maxAttempts: 3, baseDelayMs: 1000, backoff: .exponential, retryOn: [429, 503]), "GetForwardReply": RetryConfig(maxAttempts: 3, baseDelayMs: 1000, backoff: .exponential, retryOn: [429, 503]), "GetGaugeNeedle": RetryConfig(maxAttempts: 3, baseDelayMs: 1000, backoff: .exponential, retryOn: [429, 503]), diff --git a/swift/Sources/Basecamp/Generated/Models/EverythingFile.swift b/swift/Sources/Basecamp/Generated/Models/EverythingFile.swift new file mode 100644 index 000000000..aad2f9025 --- /dev/null +++ b/swift/Sources/Basecamp/Generated/Models/EverythingFile.swift @@ -0,0 +1,35 @@ +// @generated from OpenAPI spec — do not edit directly +import Foundation + +public struct EverythingFile: Codable, Sendable { + public var appDownloadUrl: String? + public var appUrl: String? + public var attachableSgid: String? + public var bookmarkUrl: String? + public var boostsCount: Int32? + public var boostsUrl: String? + public var bucket: RecordingBucket? + public var byteSize: Int? + public var commentsCount: Int32? + public var commentsUrl: String? + public var contentType: String? + public var createdAt: String? + public var creator: Person? + public var description: String? + public var descriptionAttachments: [RichTextAttachment]? + public var downloadUrl: String? + public var filename: String? + public var height: Int32? + public var id: Int? + public var inheritsStatus: Bool? + public var parent: RecordingParent? + public var position: Int32? + public var status: String? + public var subscriptionUrl: String? + public var title: String? + public var type: String? + public var updatedAt: String? + public var url: String? + public var visibleToClients: Bool? + public var width: Int32? +} diff --git a/swift/Sources/Basecamp/Generated/Services/EverythingService.swift b/swift/Sources/Basecamp/Generated/Services/EverythingService.swift new file mode 100644 index 000000000..08a538ba1 --- /dev/null +++ b/swift/Sources/Basecamp/Generated/Services/EverythingService.swift @@ -0,0 +1,184 @@ +// @generated from OpenAPI spec — do not edit directly +import Foundation + +public struct EverythingBoostsEverythingOptions: Sendable { + public var page: Int? + public var maxItems: Int? + + public init(page: Int? = nil, maxItems: Int? = nil) { + self.page = page + self.maxItems = maxItems + } +} + +public struct EverythingCheckinsEverythingOptions: Sendable { + public var page: Int? + public var maxItems: Int? + + public init(page: Int? = nil, maxItems: Int? = nil) { + self.page = page + self.maxItems = maxItems + } +} + +public struct EverythingCommentsEverythingOptions: Sendable { + public var page: Int? + public var maxItems: Int? + + public init(page: Int? = nil, maxItems: Int? = nil) { + self.page = page + self.maxItems = maxItems + } +} + +public struct EverythingFilesEverythingOptions: Sendable { + public var kind: String? + public var peopleIds: [Int]? + public var page: Int? + public var maxItems: Int? + + public init( + kind: String? = nil, + peopleIds: [Int]? = nil, + page: Int? = nil, + maxItems: Int? = nil + ) { + self.kind = kind + self.peopleIds = peopleIds + self.page = page + self.maxItems = maxItems + } +} + +public struct EverythingForwardsEverythingOptions: Sendable { + public var page: Int? + public var maxItems: Int? + + public init(page: Int? = nil, maxItems: Int? = nil) { + self.page = page + self.maxItems = maxItems + } +} + +public struct EverythingMessagesEverythingOptions: Sendable { + public var page: Int? + public var maxItems: Int? + + public init(page: Int? = nil, maxItems: Int? = nil) { + self.page = page + self.maxItems = maxItems + } +} + + +public final class EverythingService: BaseService, @unchecked Sendable { + public func everythingBoosts(options: EverythingBoostsEverythingOptions? = nil) async throws -> ListResult { + var queryItems: [URLQueryItem] = [] + if let page = options?.page { + queryItems.append(URLQueryItem(name: "page", value: String(page))) + } + return try await requestPaginated( + OperationInfo(service: "Everything", operation: "GetEverythingBoosts", resourceType: "everything_boost", isMutation: false), + path: "/boosts.json", + queryItems: queryItems.isEmpty ? nil : queryItems, + paginationOpts: options.flatMap { PaginationOptions(maxItems: $0.maxItems) }, + retryConfig: Metadata.retryConfig(for: "GetEverythingBoosts") + ) + } + + public func everythingCheckins(options: EverythingCheckinsEverythingOptions? = nil) async throws -> ListResult { + var queryItems: [URLQueryItem] = [] + if let page = options?.page { + queryItems.append(URLQueryItem(name: "page", value: String(page))) + } + return try await requestPaginated( + OperationInfo(service: "Everything", operation: "GetEverythingCheckins", resourceType: "everything_checkin", isMutation: false), + path: "/checkins.json", + queryItems: queryItems.isEmpty ? nil : queryItems, + paginationOpts: options.flatMap { PaginationOptions(maxItems: $0.maxItems) }, + retryConfig: Metadata.retryConfig(for: "GetEverythingCheckins") + ) + } + + public func everythingComments(options: EverythingCommentsEverythingOptions? = nil) async throws -> ListResult { + var queryItems: [URLQueryItem] = [] + if let page = options?.page { + queryItems.append(URLQueryItem(name: "page", value: String(page))) + } + return try await requestPaginated( + OperationInfo(service: "Everything", operation: "GetEverythingComments", resourceType: "everything_comment", isMutation: false), + path: "/comments.json", + queryItems: queryItems.isEmpty ? nil : queryItems, + paginationOpts: options.flatMap { PaginationOptions(maxItems: $0.maxItems) }, + retryConfig: Metadata.retryConfig(for: "GetEverythingComments") + ) + } + + public func everythingFiles(options: EverythingFilesEverythingOptions? = nil) async throws -> ListResult { + var queryItems: [URLQueryItem] = [] + if let kind = options?.kind { + queryItems.append(URLQueryItem(name: "kind", value: kind)) + } + if let peopleIds = options?.peopleIds { + for item in peopleIds { + queryItems.append(URLQueryItem(name: "people_ids[]", value: String(item))) + } + } + if let page = options?.page { + queryItems.append(URLQueryItem(name: "page", value: String(page))) + } + return try await requestPaginated( + OperationInfo(service: "Everything", operation: "GetEverythingFiles", resourceType: "everything_file", isMutation: false), + path: "/files.json", + queryItems: queryItems.isEmpty ? nil : queryItems, + paginationOpts: options.flatMap { PaginationOptions(maxItems: $0.maxItems) }, + retryConfig: Metadata.retryConfig(for: "GetEverythingFiles") + ) + } + + public func everythingForwards(options: EverythingForwardsEverythingOptions? = nil) async throws -> ListResult { + var queryItems: [URLQueryItem] = [] + if let page = options?.page { + queryItems.append(URLQueryItem(name: "page", value: String(page))) + } + return try await requestPaginated( + OperationInfo(service: "Everything", operation: "GetEverythingForwards", resourceType: "everything_forward", isMutation: false), + path: "/forwards.json", + queryItems: queryItems.isEmpty ? nil : queryItems, + paginationOpts: options.flatMap { PaginationOptions(maxItems: $0.maxItems) }, + retryConfig: Metadata.retryConfig(for: "GetEverythingForwards") + ) + } + + public func everythingMessages(options: EverythingMessagesEverythingOptions? = nil) async throws -> ListResult { + var queryItems: [URLQueryItem] = [] + if let page = options?.page { + queryItems.append(URLQueryItem(name: "page", value: String(page))) + } + return try await requestPaginated( + OperationInfo(service: "Everything", operation: "GetEverythingMessages", resourceType: "everything_message", isMutation: false), + path: "/messages.json", + queryItems: queryItems.isEmpty ? nil : queryItems, + paginationOpts: options.flatMap { PaginationOptions(maxItems: $0.maxItems) }, + retryConfig: Metadata.retryConfig(for: "GetEverythingMessages") + ) + } + + public func everythingOverdueCards() async throws -> [Card] { + return try await request( + OperationInfo(service: "Everything", operation: "GetEverythingOverdueCards", resourceType: "everything_overdue_card", isMutation: false), + method: "GET", + path: "/cards/overdue.json", + retryConfig: Metadata.retryConfig(for: "GetEverythingOverdueCards") + ) + } + + public func everythingOverdueTodos() async throws -> [Todo] { + return try await request( + OperationInfo(service: "Everything", operation: "GetEverythingOverdueTodos", resourceType: "everything_overdue_todo", isMutation: false), + method: "GET", + path: "/todos/overdue.json", + retryConfig: Metadata.retryConfig(for: "GetEverythingOverdueTodos") + ) + } +} diff --git a/swift/Tests/BasecampTests/GeneratedServiceTests.swift b/swift/Tests/BasecampTests/GeneratedServiceTests.swift index 205fb94d5..a758fe348 100644 --- a/swift/Tests/BasecampTests/GeneratedServiceTests.swift +++ b/swift/Tests/BasecampTests/GeneratedServiceTests.swift @@ -935,4 +935,94 @@ final class GeneratedServiceTests: XCTestCase { let sentURL = transport.lastRequest!.request.url!.absoluteString XCTAssertTrue(sentURL.hasSuffix("/my/readings/bubble_ups.json"), "Got \(sentURL)") } + + // MARK: - Everything /files.json heterogeneous feed + + // Proves runtime decode of the /files.json "everything" feed through the full + // service lifecycle (.convertFromSnakeCase): a single non-empty array carrying + // three distinct variants — a full Upload recording, a Basecamp Document + // recording, and a rich-text Attachment envelope — each asserted on real + // per-variant fields. The Upload width is float-spelled (1024.0) on the wire; + // Foundation's JSONDecoder folds that into the model's Int32 field. + func testEverythingFilesDecodesHeterogeneousFeed() async throws { + let fixture = """ + [ + { + "id": 900, + "type": "Upload", + "status": "active", + "visible_to_clients": false, + "title": "logo.png", + "inherits_status": true, + "filename": "logo.png", + "content_type": "image/png", + "byte_size": 1281, + "width": 1024.0, + "height": 768.0, + "url": "https://3.basecampapi.com/1/buckets/2/uploads/900.json", + "app_url": "https://3.basecamp.com/1/buckets/2/uploads/900", + "download_url": "https://3.basecampapi.com/1/buckets/2/uploads/900/download/logo.png", + "app_download_url": "https://storage.3.basecamp.com/1/buckets/2/uploads/900/download/logo.png", + "bucket": { "id": 2, "name": "The Leto Laptop", "type": "Project" }, + "creator": { "id": 1, "name": "Victor Cooper" } + }, + { + "id": 901, + "type": "Document", + "status": "active", + "visible_to_clients": false, + "title": "Spec", + "inherits_status": true, + "content_type": "text/html", + "url": "https://3.basecampapi.com/1/buckets/2/documents/901.json", + "app_url": "https://3.basecamp.com/1/buckets/2/documents/901", + "bucket": { "id": 2, "name": "The Leto Laptop", "type": "Project" }, + "creator": { "id": 1, "name": "Victor Cooper" } + }, + { + "id": 902, + "type": "Attachment", + "attachable_sgid": "sgid-902", + "filename": "chart.avif", + "content_type": "image/avif", + "byte_size": 4096, + "width": null, + "height": null, + "download_url": "https://storage.3.basecamp.com/1/blobs/902/download/chart.avif", + "parent": { + "id": 800, + "title": "A message", + "type": "Message", + "app_url": "https://3.basecamp.com/1/buckets/2/messages/800", + "url": "https://3.basecampapi.com/1/buckets/2/messages/800.json" + } + } + ] + """ + let transport = MockTransport(statusCode: 200, data: Data(fixture.utf8), + headers: ["X-Total-Count": "3"]) + let account = makeTestAccountClient(transport: transport) + + let files = try await account.everything.everythingFiles() + XCTAssertEqual(files.count, 3) + + // [0]: full Upload recording (float-spelled width folds into Int32) + XCTAssertEqual(files[0].type, "Upload") + XCTAssertEqual(files[0].filename, "logo.png") + XCTAssertNotNil(files[0].appDownloadUrl) + XCTAssertEqual(files[0].width, 1024) + + // [1]: Basecamp Document recording + XCTAssertEqual(files[1].type, "Document") + XCTAssertEqual(files[1].title, "Spec") + + // [2]: rich-text Attachment envelope + XCTAssertEqual(files[2].type, "Attachment") + XCTAssertEqual(files[2].attachableSgid, "sgid-902") + XCTAssertNotNil(files[2].parent) + XCTAssertNil(files[2].width) + + let sentURL = transport.lastRequest!.request.url!.absoluteString + XCTAssertTrue(sentURL.hasSuffix("/files.json"), "Got \(sentURL)") + } } diff --git a/typescript/scripts/generate-services.ts b/typescript/scripts/generate-services.ts index 559eb266d..0640f470b 100644 --- a/typescript/scripts/generate-services.ts +++ b/typescript/scripts/generate-services.ts @@ -459,6 +459,8 @@ const TYPE_ALIASES: Record ClientReply: ["ClientReply", "entity"], TimelineEvent: ["TimelineEvent", "entity"], Notification: ["Notification", "entity"], + Boost: ["Boost", "entity"], + EverythingFile: ["EverythingFile", "entity"], TimesheetEntry: ["TimesheetEntry", "entity"], }; diff --git a/typescript/src/client.ts b/typescript/src/client.ts index 36190a3c0..3ea57321a 100644 --- a/typescript/src/client.ts +++ b/typescript/src/client.ts @@ -59,6 +59,7 @@ import { TodolistGroupsService } from "./generated/services/todolist-groups.js"; import { ToolsService } from "./generated/services/tools.js"; import { TimesheetsService } from "./generated/services/timesheets.js"; import { TimelineService } from "./generated/services/timeline.js"; +import { EverythingService } from "./generated/services/everything.js"; import { ClientVisibilityService } from "./generated/services/client-visibility.js"; import { BoostsService } from "./generated/services/boosts.js"; import { AccountService } from "./generated/services/account.js"; @@ -174,6 +175,8 @@ export interface BasecampClient extends RawClient { readonly timesheets: TimesheetsService; /** Timeline service - get project timeline */ readonly timeline: TimelineService; + /** Everything service - account-wide aggregate listings */ + readonly everything: EverythingService; /** Client visibility service - manage client visibility */ readonly clientVisibility: ClientVisibilityService; /** Boosts service - manage recording boosts */ @@ -409,6 +412,7 @@ export function createBasecampClient(options: BasecampClientOptions): BasecampCl defineService("tools", () => new ToolsService(client, hooks, fetchPage, maxPages)); defineService("timesheets", () => new TimesheetsService(client, hooks, fetchPage, maxPages)); defineService("timeline", () => new TimelineService(client, hooks, fetchPage, maxPages)); + defineService("everything", () => new EverythingService(client, hooks, fetchPage, maxPages)); defineService("clientVisibility", () => new ClientVisibilityService(client, hooks, fetchPage, maxPages)); defineService("boosts", () => new BoostsService(client, hooks, fetchPage, maxPages)); defineService("account", () => new AccountService(client, hooks, fetchPage, maxPages, authenticatedFetch, baseUrl)); diff --git a/typescript/src/generated/metadata.ts b/typescript/src/generated/metadata.ts index 145911649..302d49371 100644 --- a/typescript/src/generated/metadata.ts +++ b/typescript/src/generated/metadata.ts @@ -37,7 +37,7 @@ export interface MetadataOutput { const metadata: MetadataOutput = { "$schema": "https://basecamp.com/schemas/sdk-metadata.json", "version": "1.0.0", - "generated": "2026-07-25T17:41:27.970Z", + "generated": "2026-07-25T17:48:05.683Z", "operations": { "GetAccount": { "retry": { @@ -103,6 +103,22 @@ const metadata: MetadataOutput = { ] } }, + "GetEverythingBoosts": { + "retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + }, + "pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + } + }, "GetBoost": { "retry": { "maxAttempts": 3, @@ -454,6 +470,17 @@ const metadata: MetadataOutput = { ] } }, + "GetEverythingOverdueCards": { + "retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, "ListMessageTypes": { "retry": { "maxAttempts": 3, @@ -706,6 +733,22 @@ const metadata: MetadataOutput = { ] } }, + "GetEverythingCheckins": { + "retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + }, + "pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + } + }, "ListPingablePeople": { "retry": { "maxAttempts": 3, @@ -803,6 +846,22 @@ const metadata: MetadataOutput = { ] } }, + "GetEverythingComments": { + "retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + }, + "pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + } + }, "GetComment": { "retry": { "maxAttempts": 3, @@ -892,6 +951,38 @@ const metadata: MetadataOutput = { "natural": true } }, + "GetEverythingFiles": { + "retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + }, + "pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + } + }, + "GetEverythingForwards": { + "retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + }, + "pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + } + }, "GetGaugeNeedle": { "retry": { "maxAttempts": 3, @@ -1095,6 +1186,22 @@ const metadata: MetadataOutput = { ] } }, + "GetEverythingMessages": { + "retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + }, + "pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + } + }, "GetMessage": { "retry": { "maxAttempts": 3, @@ -2424,6 +2531,17 @@ const metadata: MetadataOutput = { ] } }, + "GetEverythingOverdueTodos": { + "retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, "GetTodo": { "retry": { "maxAttempts": 3, diff --git a/typescript/src/generated/openapi-stripped.json b/typescript/src/generated/openapi-stripped.json index 7bc0dfec3..27886656e 100644 --- a/typescript/src/generated/openapi-stripped.json +++ b/typescript/src/generated/openapi-stripped.json @@ -433,6 +433,93 @@ } } }, + "/boosts.json": { + "get": { + "description": "Get every boost across all accessible projects, newest-first (paginated).\nEach boost carries its `booster` and the `recording` it boosts.", + "operationId": "GetEverythingBoosts", + "parameters": [ + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "GetEverythingBoosts 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetEverythingBoostsResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Everything" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, "/boosts/{boostId}": { "delete": { "description": "Delete a boost", @@ -3082,6 +3169,67 @@ } } }, + "/cards/overdue.json": { + "get": { + "description": "Get every overdue card across all accessible projects, oldest-due-date-first.\nA complete, unpaginated array; each item embeds its `bucket`.", + "operationId": "GetEverythingOverdueCards", + "parameters": [], + "responses": { + "200": { + "description": "GetEverythingOverdueCards 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetEverythingOverdueCardsResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Everything" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, "/categories.json": { "get": { "description": "List message types in a project\n\n**Pagination**: Uses Link header (RFC5988). Follow the `next` rel URL\nto fetch additional pages. X-Total-Count header provides total count.", @@ -4835,6 +4983,93 @@ } } }, + "/checkins.json": { + "get": { + "description": "Get every automatic check-in answer across all accessible projects,\nnewest-first (paginated). Each item embeds its `bucket`.", + "operationId": "GetEverythingCheckins", + "parameters": [ + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "GetEverythingCheckins 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetEverythingCheckinsResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Everything" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, "/circles/people.json": { "get": { "description": "List all account users who can be pinged\n\n**Pagination**: Uses Link header (RFC5988). Follow the `next` rel URL\nto fetch additional pages. X-Total-Count header provides total count.", @@ -5439,28 +5674,29 @@ } } }, - "/comments/{commentId}": { + "/comments.json": { "get": { - "description": "Get a single comment by id", - "operationId": "GetComment", + "description": "Get every comment across all accessible projects, newest-first (paginated).\nEach item embeds its `bucket`.", + "operationId": "GetEverythingComments", "parameters": [ { - "name": "commentId", - "in": "path", + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1.", "schema": { "type": "integer", - "format": "int64" - }, - "required": true + "description": "Page number for paginating through results. Defaults to 1.", + "format": "int32" + } } ], "responses": { "200": { - "description": "GetComment 200 response", + "description": "GetEverythingComments 200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/GetCommentResponseContent" + "$ref": "#/components/schemas/GetEverythingCommentsResponseContent" } } } @@ -5485,12 +5721,12 @@ } } }, - "404": { - "description": "NotFoundError 404 response", + "429": { + "description": "RateLimitError 429 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/NotFoundErrorResponseContent" + "$ref": "#/components/schemas/RateLimitErrorResponseContent" } } } @@ -5507,8 +5743,13 @@ } }, "tags": [ - "Messages" + "Everything" ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, "x-basecamp-retry": { "maxAttempts": 3, "baseDelayMs": 1000, @@ -5518,23 +5759,104 @@ 503 ] } - }, - "put": { - "description": "Update an existing comment", - "operationId": "UpdateComment", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UpdateCommentRequestContent" - } - } - }, - "required": true - }, - "parameters": [ - { - "name": "commentId", + } + }, + "/comments/{commentId}": { + "get": { + "description": "Get a single comment by id", + "operationId": "GetComment", + "parameters": [ + { + "name": "commentId", + "in": "path", + "schema": { + "type": "integer", + "format": "int64" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "GetComment 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetCommentResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "404": { + "description": "NotFoundError 404 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotFoundErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Messages" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + }, + "put": { + "description": "Update an existing comment", + "operationId": "UpdateComment", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateCommentRequestContent" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "commentId", "in": "path", "schema": { "type": "integer", @@ -6062,6 +6384,205 @@ } } }, + "/files.json": { + "get": { + "description": "Get every file recording across all accessible projects, newest-first\n(paginated). Heterogeneous: uploads and Basecamp documents carry their\nstandard recording shapes, while rich-text attachments are wrapped in a\nrecording envelope plus an `attachable_sgid` and blob metadata. Modeled as\nan optional-field superset (EverythingFile) so one element type decodes any\nvariant.", + "operationId": "GetEverythingFiles", + "parameters": [ + { + "name": "kind", + "in": "query", + "description": "Filter by file kind: all (default), images, pdfs, documents, or videos.", + "schema": { + "type": "string", + "description": "Filter by file kind: all (default), images, pdfs, documents, or videos." + } + }, + { + "name": "people_ids[]", + "in": "query", + "description": "Restrict to files created by the given people (repeatable).", + "style": "form", + "schema": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + }, + "description": "Restrict to files created by the given people (repeatable).", + "x-go-type-skip-optional-pointer": false + }, + "explode": true + }, + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "GetEverythingFiles 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetEverythingFilesResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Everything" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/forwards.json": { + "get": { + "description": "Get every inbox forward across all accessible projects, newest-first\n(paginated). Each item embeds its `bucket`.", + "operationId": "GetEverythingForwards", + "parameters": [ + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1.", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "GetEverythingForwards 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetEverythingForwardsResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Everything" + ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, "/gauge_needles/{needleId}": { "delete": { "description": "Destroy a gauge needle", @@ -7407,18 +7928,110 @@ "in": "path", "schema": { "type": "integer", - "format": "int64" - }, - "required": true + "format": "int64" + }, + "required": true + } + ], + "responses": { + "201": { + "description": "CreateMessage 201 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateMessageResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "422": { + "description": "ValidationError 422 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationErrorResponseContent" + } + } + } + }, + "429": { + "description": "RateLimitError 429 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RateLimitErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Messages" + ], + "x-basecamp-retry": { + "maxAttempts": 2, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, + "/messages.json": { + "get": { + "description": "Get every message across all accessible projects, newest-first (paginated).\nEach item embeds its `bucket` for project context.", + "operationId": "GetEverythingMessages", + "parameters": [ + { + "name": "page", + "in": "query", + "description": "Page number for paginating through results. Defaults to 1.", + "schema": { + "type": "integer", + "description": "Page number for paginating through results. Defaults to 1.", + "format": "int32" + } } ], "responses": { - "201": { - "description": "CreateMessage 201 response", + "200": { + "description": "GetEverythingMessages 200 response", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/CreateMessageResponseContent" + "$ref": "#/components/schemas/GetEverythingMessagesResponseContent" } } } @@ -7443,16 +8056,6 @@ } } }, - "422": { - "description": "ValidationError 422 response", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ValidationErrorResponseContent" - } - } - } - }, "429": { "description": "RateLimitError 429 response", "content": { @@ -7475,10 +8078,15 @@ } }, "tags": [ - "Messages" + "Everything" ], + "x-basecamp-pagination": { + "style": "link", + "totalCountHeader": "X-Total-Count", + "maxPageSize": 50 + }, "x-basecamp-retry": { - "maxAttempts": 2, + "maxAttempts": 3, "baseDelayMs": 1000, "backoff": "exponential", "retryOn": [ @@ -16785,6 +17393,67 @@ } } }, + "/todos/overdue.json": { + "get": { + "description": "Get every overdue to-do across all accessible projects, oldest-due-date-first.\nA complete, unpaginated array; each item embeds its `bucket`.", + "operationId": "GetEverythingOverdueTodos", + "parameters": [], + "responses": { + "200": { + "description": "GetEverythingOverdueTodos 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetEverythingOverdueTodosResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "403": { + "description": "ForbiddenError 403 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Everything" + ], + "x-basecamp-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, "/todos/{todoId}": { "delete": { "description": "Trash a todo (returns 204 No Content)", @@ -21568,6 +22237,144 @@ } } }, + "EverythingFile": { + "type": "object", + "description": "A single item in the /files.json feed. An optional-field superset over three\nwire variants — a full Upload recording, a Basecamp Document recording, and a\nrich-text attachment wrapped in a recording envelope (distinguished by\n`attachable_sgid` and blob metadata). Every field is optional; a given\ninstance populates only the fields of the variant it represents. Unknown\nfields are ignored by every SDK decoder, so the superset need not enumerate\nevery field of the Upload/Document recordings.", + "properties": { + "id": { + "type": "integer", + "description": "Recording (Upload/Document) or attachment id.", + "format": "int64", + "x-go-type-skip-optional-pointer": false + }, + "status": { + "type": "string" + }, + "visible_to_clients": { + "type": "boolean", + "x-go-type-skip-optional-pointer": false + }, + "created_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + }, + "x-go-type-skip-optional-pointer": false + }, + "updated_at": { + "type": "string", + "x-go-type": "time.Time", + "x-go-type-import": { + "path": "time" + }, + "x-go-type-skip-optional-pointer": false + }, + "title": { + "type": "string" + }, + "inherits_status": { + "type": "boolean", + "x-go-type-skip-optional-pointer": false + }, + "type": { + "type": "string", + "description": "\"Upload\", \"Document\", or \"Attachment\"." + }, + "url": { + "type": "string" + }, + "app_url": { + "type": "string" + }, + "bookmark_url": { + "type": "string" + }, + "subscription_url": { + "type": "string" + }, + "comments_count": { + "type": "integer", + "format": "int32" + }, + "comments_url": { + "type": "string" + }, + "boosts_count": { + "type": "integer", + "format": "int32" + }, + "boosts_url": { + "type": "string" + }, + "position": { + "type": "integer", + "format": "int32" + }, + "parent": { + "$ref": "#/components/schemas/RecordingParent" + }, + "bucket": { + "$ref": "#/components/schemas/RecordingBucket" + }, + "creator": { + "$ref": "#/components/schemas/Person" + }, + "attachable_sgid": { + "type": "string", + "description": "Present on the rich-text attachment variant: signed global id of the\nattachment (uploads/documents omit it)." + }, + "content_type": { + "type": "string" + }, + "byte_size": { + "type": "integer", + "format": "int64" + }, + "filename": { + "type": "string" + }, + "download_url": { + "type": "string", + "x-basecamp-auth-routable-url": {} + }, + "app_download_url": { + "type": "string" + }, + "width": { + "type": "integer", + "description": "Pixel width; null for non-image blobs and may be float-spelled (1024.0).", + "format": "int32", + "nullable": true, + "x-go-type": "types.FlexInt", + "x-go-type-import": { + "path": "github.com/basecamp/basecamp-sdk/go/pkg/types" + }, + "x-go-type-skip-optional-pointer": false + }, + "height": { + "type": "integer", + "description": "Pixel height; null for non-image blobs and may be float-spelled (1024.0).", + "format": "int32", + "nullable": true, + "x-go-type": "types.FlexInt", + "x-go-type-import": { + "path": "github.com/basecamp/basecamp-sdk/go/pkg/types" + }, + "x-go-type-skip-optional-pointer": false + }, + "description": { + "type": "string", + "description": "Rich-text description (upload/document variants)." + }, + "description_attachments": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RichTextAttachment" + } + } + } + }, "FirstWeekDay": { "type": "string", "enum": [ @@ -22085,6 +22892,54 @@ "GetDocumentResponseContent": { "$ref": "#/components/schemas/Document" }, + "GetEverythingBoostsResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Boost" + } + }, + "GetEverythingCheckinsResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Recording" + } + }, + "GetEverythingCommentsResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Recording" + } + }, + "GetEverythingFilesResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EverythingFile" + } + }, + "GetEverythingForwardsResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Recording" + } + }, + "GetEverythingMessagesResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Recording" + } + }, + "GetEverythingOverdueCardsResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Card" + } + }, + "GetEverythingOverdueTodosResponseContent": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Todo" + } + }, "GetForwardReplyResponseContent": { "$ref": "#/components/schemas/ForwardReply" }, diff --git a/typescript/src/generated/path-mapping.ts b/typescript/src/generated/path-mapping.ts index 723b74331..3a5c16956 100644 --- a/typescript/src/generated/path-mapping.ts +++ b/typescript/src/generated/path-mapping.ts @@ -11,6 +11,7 @@ export const PATH_TO_OPERATION: Record = { "DELETE:/{accountId}/account/logo.json": "RemoveAccountLogo", "PUT:/{accountId}/account/logo.json": "UpdateAccountLogo", "PUT:/{accountId}/account/name.json": "UpdateAccountName", + "GET:/{accountId}/boosts.json": "GetEverythingBoosts", "DELETE:/{accountId}/boosts/{boostId}": "DeleteBoost", "GET:/{accountId}/boosts/{boostId}": "GetBoost", "POST:/{accountId}/buckets/{bucketId}/card_tables/{cardTableId}/wormholes.json": "CreateWormhole", @@ -39,6 +40,7 @@ export const PATH_TO_OPERATION: Record = { "GET:/{accountId}/card_tables/steps/{stepId}": "GetCardStep", "PUT:/{accountId}/card_tables/steps/{stepId}": "UpdateCardStep", "PUT:/{accountId}/card_tables/steps/{stepId}/completions.json": "SetCardStepCompletion", + "GET:/{accountId}/cards/overdue.json": "GetEverythingOverdueCards", "GET:/{accountId}/categories.json": "ListMessageTypes", "POST:/{accountId}/categories.json": "CreateMessageType", "DELETE:/{accountId}/categories/{typeId}": "DeleteMessageType", @@ -58,12 +60,14 @@ export const PATH_TO_OPERATION: Record = { "PUT:/{accountId}/chats/{campfireId}/lines/{lineId}": "UpdateCampfireLine", "GET:/{accountId}/chats/{campfireId}/uploads.json": "ListCampfireUploads", "POST:/{accountId}/chats/{campfireId}/uploads.json": "CreateCampfireUpload", + "GET:/{accountId}/checkins.json": "GetEverythingCheckins", "GET:/{accountId}/client/approvals.json": "ListClientApprovals", "GET:/{accountId}/client/approvals/{approvalId}": "GetClientApproval", "GET:/{accountId}/client/correspondences.json": "ListClientCorrespondences", "GET:/{accountId}/client/correspondences/{correspondenceId}": "GetClientCorrespondence", "GET:/{accountId}/client/recordings/{recordingId}/replies.json": "ListClientReplies", "GET:/{accountId}/client/recordings/{recordingId}/replies/{replyId}": "GetClientReply", + "GET:/{accountId}/comments.json": "GetEverythingComments", "GET:/{accountId}/comments/{commentId}": "GetComment", "PUT:/{accountId}/comments/{commentId}": "UpdateComment", "DELETE:/{accountId}/dock/tools/{toolId}": "DeleteTool", @@ -71,6 +75,8 @@ export const PATH_TO_OPERATION: Record = { "PUT:/{accountId}/dock/tools/{toolId}": "UpdateTool", "GET:/{accountId}/documents/{documentId}": "GetDocument", "PUT:/{accountId}/documents/{documentId}": "UpdateDocument", + "GET:/{accountId}/files.json": "GetEverythingFiles", + "GET:/{accountId}/forwards.json": "GetEverythingForwards", "DELETE:/{accountId}/gauge_needles/{needleId}": "DestroyGaugeNeedle", "GET:/{accountId}/gauge_needles/{needleId}": "GetGaugeNeedle", "PUT:/{accountId}/gauge_needles/{needleId}": "UpdateGaugeNeedle", @@ -87,6 +93,7 @@ export const PATH_TO_OPERATION: Record = { "GET:/{accountId}/message_boards/{boardId}": "GetMessageBoard", "GET:/{accountId}/message_boards/{boardId}/messages.json": "ListMessages", "POST:/{accountId}/message_boards/{boardId}/messages.json": "CreateMessage", + "GET:/{accountId}/messages.json": "GetEverythingMessages", "GET:/{accountId}/messages/{messageId}": "GetMessage", "PUT:/{accountId}/messages/{messageId}": "UpdateMessage", "GET:/{accountId}/question_answers/{answerId}": "GetAnswer", @@ -153,6 +160,7 @@ export const PATH_TO_OPERATION: Record = { "DELETE:/{accountId}/todos/{todoId}/completion.json": "UncompleteTodo", "POST:/{accountId}/todos/{todoId}/completion.json": "CompleteTodo", "PUT:/{accountId}/todos/{todoId}/position.json": "RepositionTodo", + "GET:/{accountId}/todos/overdue.json": "GetEverythingOverdueTodos", "GET:/{accountId}/todosets/{todosetId}": "GetTodoset", "GET:/{accountId}/todosets/{todosetId}/hill.json": "GetHillChart", "PUT:/{accountId}/todosets/{todosetId}/hills/settings.json": "UpdateHillChartSettings", diff --git a/typescript/src/generated/schema.d.ts b/typescript/src/generated/schema.d.ts index 7811ff1b8..c8e5f3f97 100644 --- a/typescript/src/generated/schema.d.ts +++ b/typescript/src/generated/schema.d.ts @@ -77,6 +77,26 @@ export interface paths { patch?: never; trace?: never; }; + "/boosts.json": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * @description Get every boost across all accessible projects, newest-first (paginated). + * Each boost carries its `booster` and the `recording` it boosts. + */ + get: operations["GetEverythingBoosts"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/boosts/{boostId}": { parameters: { query?: never; @@ -425,6 +445,26 @@ export interface paths { patch?: never; trace?: never; }; + "/cards/overdue.json": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * @description Get every overdue card across all accessible projects, oldest-due-date-first. + * A complete, unpaginated array; each item embeds its `bucket`. + */ + get: operations["GetEverythingOverdueCards"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/categories.json": { parameters: { query?: never; @@ -621,6 +661,26 @@ export interface paths { patch?: never; trace?: never; }; + "/checkins.json": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * @description Get every automatic check-in answer across all accessible projects, + * newest-first (paginated). Each item embeds its `bucket`. + */ + get: operations["GetEverythingCheckins"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/circles/people.json": { parameters: { query?: never; @@ -760,6 +820,26 @@ export interface paths { patch?: never; trace?: never; }; + "/comments.json": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * @description Get every comment across all accessible projects, newest-first (paginated). + * Each item embeds its `bucket`. + */ + get: operations["GetEverythingComments"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/comments/{commentId}": { parameters: { query?: never; @@ -815,6 +895,50 @@ export interface paths { patch?: never; trace?: never; }; + "/files.json": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * @description Get every file recording across all accessible projects, newest-first + * (paginated). Heterogeneous: uploads and Basecamp documents carry their + * standard recording shapes, while rich-text attachments are wrapped in a + * recording envelope plus an `attachable_sgid` and blob metadata. Modeled as + * an optional-field superset (EverythingFile) so one element type decodes any + * variant. + */ + get: operations["GetEverythingFiles"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/forwards.json": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * @description Get every inbox forward across all accessible projects, newest-first + * (paginated). Each item embeds its `bucket`. + */ + get: operations["GetEverythingForwards"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/gauge_needles/{needleId}": { parameters: { query?: never; @@ -1006,6 +1130,26 @@ export interface paths { patch?: never; trace?: never; }; + "/messages.json": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * @description Get every message across all accessible projects, newest-first (paginated). + * Each item embeds its `bucket` for project context. + */ + get: operations["GetEverythingMessages"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/messages/{messageId}": { parameters: { query?: never; @@ -2317,6 +2461,26 @@ export interface paths { patch?: never; trace?: never; }; + "/todos/overdue.json": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * @description Get every overdue to-do across all accessible projects, oldest-due-date-first. + * A complete, unpaginated array; each item embeds its `bucket`. + */ + get: operations["GetEverythingOverdueTodos"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/todos/{todoId}": { parameters: { query?: never; @@ -3277,6 +3441,69 @@ export interface components { removed_person_ids?: number[]; notified_recipient_ids?: number[]; }; + /** + * @description A single item in the /files.json feed. An optional-field superset over three + * wire variants — a full Upload recording, a Basecamp Document recording, and a + * rich-text attachment wrapped in a recording envelope (distinguished by + * `attachable_sgid` and blob metadata). Every field is optional; a given + * instance populates only the fields of the variant it represents. Unknown + * fields are ignored by every SDK decoder, so the superset need not enumerate + * every field of the Upload/Document recordings. + */ + EverythingFile: { + /** + * Format: int64 + * @description Recording (Upload/Document) or attachment id. + */ + id?: number; + status?: string; + visible_to_clients?: boolean; + created_at?: string; + updated_at?: string; + title?: string; + inherits_status?: boolean; + /** @description "Upload", "Document", or "Attachment". */ + type?: string; + url?: string; + app_url?: string; + bookmark_url?: string; + subscription_url?: string; + /** Format: int32 */ + comments_count?: number; + comments_url?: string; + /** Format: int32 */ + boosts_count?: number; + boosts_url?: string; + /** Format: int32 */ + position?: number; + parent?: components["schemas"]["RecordingParent"]; + bucket?: components["schemas"]["RecordingBucket"]; + creator?: components["schemas"]["Person"]; + /** + * @description Present on the rich-text attachment variant: signed global id of the + * attachment (uploads/documents omit it). + */ + attachable_sgid?: string; + content_type?: string; + /** Format: int64 */ + byte_size?: number; + filename?: string; + download_url?: string; + app_download_url?: string; + /** + * Format: int32 + * @description Pixel width; null for non-image blobs and may be float-spelled (1024.0). + */ + width?: number | null; + /** + * Format: int32 + * @description Pixel height; null for non-image blobs and may be float-spelled (1024.0). + */ + height?: number | null; + /** @description Rich-text description (upload/document variants). */ + description?: string; + description_attachments?: components["schemas"]["RichTextAttachment"][]; + }; /** @enum {string} */ FirstWeekDay: "Sunday" | "Monday" | "Tuesday" | "Wednesday" | "Thursday" | "Friday" | "Saturday"; ForbiddenErrorResponseContent: { @@ -3429,6 +3656,14 @@ export interface components { GetClientReplyResponseContent: components["schemas"]["ClientReply"]; GetCommentResponseContent: components["schemas"]["Comment"]; GetDocumentResponseContent: components["schemas"]["Document"]; + GetEverythingBoostsResponseContent: components["schemas"]["Boost"][]; + GetEverythingCheckinsResponseContent: components["schemas"]["Recording"][]; + GetEverythingCommentsResponseContent: components["schemas"]["Recording"][]; + GetEverythingFilesResponseContent: components["schemas"]["EverythingFile"][]; + GetEverythingForwardsResponseContent: components["schemas"]["Recording"][]; + GetEverythingMessagesResponseContent: components["schemas"]["Recording"][]; + GetEverythingOverdueCardsResponseContent: components["schemas"]["Card"][]; + GetEverythingOverdueTodosResponseContent: components["schemas"]["Todo"][]; GetForwardReplyResponseContent: components["schemas"]["ForwardReply"]; GetForwardResponseContent: components["schemas"]["Forward"]; GetGaugeNeedleResponseContent: components["schemas"]["GaugeNeedle"]; @@ -5351,6 +5586,65 @@ export interface operations { }; }; }; + GetEverythingBoosts: { + parameters: { + query?: { + /** @description Page number for paginating through results. Defaults to 1. */ + page?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description GetEverythingBoosts 200 response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["GetEverythingBoostsResponseContent"]; + }; + }; + /** @description UnauthorizedError 401 response */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["UnauthorizedErrorResponseContent"]; + }; + }; + /** @description ForbiddenError 403 response */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ForbiddenErrorResponseContent"]; + }; + }; + /** @description RateLimitError 429 response */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["RateLimitErrorResponseContent"]; + }; + }; + /** @description InternalServerError 500 response */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["InternalServerErrorResponseContent"]; + }; + }; + }; + }; GetBoost: { parameters: { query?: never; @@ -7216,6 +7510,53 @@ export interface operations { }; }; }; + GetEverythingOverdueCards: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description GetEverythingOverdueCards 200 response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["GetEverythingOverdueCardsResponseContent"]; + }; + }; + /** @description UnauthorizedError 401 response */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["UnauthorizedErrorResponseContent"]; + }; + }; + /** @description ForbiddenError 403 response */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ForbiddenErrorResponseContent"]; + }; + }; + /** @description InternalServerError 500 response */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["InternalServerErrorResponseContent"]; + }; + }; + }; + }; ListMessageTypes: { parameters: { query?: never; @@ -8423,6 +8764,65 @@ export interface operations { }; }; }; + GetEverythingCheckins: { + parameters: { + query?: { + /** @description Page number for paginating through results. Defaults to 1. */ + page?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description GetEverythingCheckins 200 response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["GetEverythingCheckinsResponseContent"]; + }; + }; + /** @description UnauthorizedError 401 response */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["UnauthorizedErrorResponseContent"]; + }; + }; + /** @description ForbiddenError 403 response */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ForbiddenErrorResponseContent"]; + }; + }; + /** @description RateLimitError 429 response */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["RateLimitErrorResponseContent"]; + }; + }; + /** @description InternalServerError 500 response */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["InternalServerErrorResponseContent"]; + }; + }; + }; + }; ListPingablePeople: { parameters: { query?: never; @@ -8645,7 +9045,65 @@ export interface operations { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["RateLimitErrorResponseContent"]; + "application/json": components["schemas"]["RateLimitErrorResponseContent"]; + }; + }; + /** @description InternalServerError 500 response */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["InternalServerErrorResponseContent"]; + }; + }; + }; + }; + GetClientCorrespondence: { + parameters: { + query?: never; + header?: never; + path: { + correspondenceId: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description GetClientCorrespondence 200 response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["GetClientCorrespondenceResponseContent"]; + }; + }; + /** @description UnauthorizedError 401 response */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["UnauthorizedErrorResponseContent"]; + }; + }; + /** @description ForbiddenError 403 response */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ForbiddenErrorResponseContent"]; + }; + }; + /** @description NotFoundError 404 response */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["NotFoundErrorResponseContent"]; }; }; /** @description InternalServerError 500 response */ @@ -8659,24 +9117,24 @@ export interface operations { }; }; }; - GetClientCorrespondence: { + ListClientReplies: { parameters: { query?: never; header?: never; path: { - correspondenceId: number; + recordingId: number; }; cookie?: never; }; requestBody?: never; responses: { - /** @description GetClientCorrespondence 200 response */ + /** @description ListClientReplies 200 response */ 200: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["GetClientCorrespondenceResponseContent"]; + "application/json": components["schemas"]["ListClientRepliesResponseContent"]; }; }; /** @description UnauthorizedError 401 response */ @@ -8697,13 +9155,13 @@ export interface operations { "application/json": components["schemas"]["ForbiddenErrorResponseContent"]; }; }; - /** @description NotFoundError 404 response */ - 404: { + /** @description RateLimitError 429 response */ + 429: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["NotFoundErrorResponseContent"]; + "application/json": components["schemas"]["RateLimitErrorResponseContent"]; }; }; /** @description InternalServerError 500 response */ @@ -8717,24 +9175,25 @@ export interface operations { }; }; }; - ListClientReplies: { + GetClientReply: { parameters: { query?: never; header?: never; path: { recordingId: number; + replyId: number; }; cookie?: never; }; requestBody?: never; responses: { - /** @description ListClientReplies 200 response */ + /** @description GetClientReply 200 response */ 200: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["ListClientRepliesResponseContent"]; + "application/json": components["schemas"]["GetClientReplyResponseContent"]; }; }; /** @description UnauthorizedError 401 response */ @@ -8755,13 +9214,13 @@ export interface operations { "application/json": components["schemas"]["ForbiddenErrorResponseContent"]; }; }; - /** @description RateLimitError 429 response */ - 429: { + /** @description NotFoundError 404 response */ + 404: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["RateLimitErrorResponseContent"]; + "application/json": components["schemas"]["NotFoundErrorResponseContent"]; }; }; /** @description InternalServerError 500 response */ @@ -8775,25 +9234,25 @@ export interface operations { }; }; }; - GetClientReply: { + GetEverythingComments: { parameters: { - query?: never; - header?: never; - path: { - recordingId: number; - replyId: number; + query?: { + /** @description Page number for paginating through results. Defaults to 1. */ + page?: number; }; + header?: never; + path?: never; cookie?: never; }; requestBody?: never; responses: { - /** @description GetClientReply 200 response */ + /** @description GetEverythingComments 200 response */ 200: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["GetClientReplyResponseContent"]; + "application/json": components["schemas"]["GetEverythingCommentsResponseContent"]; }; }; /** @description UnauthorizedError 401 response */ @@ -8814,13 +9273,13 @@ export interface operations { "application/json": components["schemas"]["ForbiddenErrorResponseContent"]; }; }; - /** @description NotFoundError 404 response */ - 404: { + /** @description RateLimitError 429 response */ + 429: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["NotFoundErrorResponseContent"]; + "application/json": components["schemas"]["RateLimitErrorResponseContent"]; }; }; /** @description InternalServerError 500 response */ @@ -9277,6 +9736,128 @@ export interface operations { }; }; }; + GetEverythingFiles: { + parameters: { + query?: { + /** @description Filter by file kind: all (default), images, pdfs, documents, or videos. */ + kind?: string; + /** @description Restrict to files created by the given people (repeatable). */ + "people_ids[]"?: number[]; + /** @description Page number for paginating through results. Defaults to 1. */ + page?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description GetEverythingFiles 200 response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["GetEverythingFilesResponseContent"]; + }; + }; + /** @description UnauthorizedError 401 response */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["UnauthorizedErrorResponseContent"]; + }; + }; + /** @description ForbiddenError 403 response */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ForbiddenErrorResponseContent"]; + }; + }; + /** @description RateLimitError 429 response */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["RateLimitErrorResponseContent"]; + }; + }; + /** @description InternalServerError 500 response */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["InternalServerErrorResponseContent"]; + }; + }; + }; + }; + GetEverythingForwards: { + parameters: { + query?: { + /** @description Page number for paginating through results. Defaults to 1. */ + page?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description GetEverythingForwards 200 response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["GetEverythingForwardsResponseContent"]; + }; + }; + /** @description UnauthorizedError 401 response */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["UnauthorizedErrorResponseContent"]; + }; + }; + /** @description ForbiddenError 403 response */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ForbiddenErrorResponseContent"]; + }; + }; + /** @description RateLimitError 429 response */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["RateLimitErrorResponseContent"]; + }; + }; + /** @description InternalServerError 500 response */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["InternalServerErrorResponseContent"]; + }; + }; + }; + }; GetGaugeNeedle: { parameters: { query?: never; @@ -10287,6 +10868,65 @@ export interface operations { }; }; }; + GetEverythingMessages: { + parameters: { + query?: { + /** @description Page number for paginating through results. Defaults to 1. */ + page?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description GetEverythingMessages 200 response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["GetEverythingMessagesResponseContent"]; + }; + }; + /** @description UnauthorizedError 401 response */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["UnauthorizedErrorResponseContent"]; + }; + }; + /** @description ForbiddenError 403 response */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ForbiddenErrorResponseContent"]; + }; + }; + /** @description RateLimitError 429 response */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["RateLimitErrorResponseContent"]; + }; + }; + /** @description InternalServerError 500 response */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["InternalServerErrorResponseContent"]; + }; + }; + }; + }; GetMessage: { parameters: { query?: never; @@ -16661,6 +17301,53 @@ export interface operations { }; }; }; + GetEverythingOverdueTodos: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description GetEverythingOverdueTodos 200 response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["GetEverythingOverdueTodosResponseContent"]; + }; + }; + /** @description UnauthorizedError 401 response */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["UnauthorizedErrorResponseContent"]; + }; + }; + /** @description ForbiddenError 403 response */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ForbiddenErrorResponseContent"]; + }; + }; + /** @description InternalServerError 500 response */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["InternalServerErrorResponseContent"]; + }; + }; + }; + }; GetTodo: { parameters: { query?: never; diff --git a/typescript/src/generated/services/boosts.ts b/typescript/src/generated/services/boosts.ts index 58b42fe5c..11bba775c 100644 --- a/typescript/src/generated/services/boosts.ts +++ b/typescript/src/generated/services/boosts.ts @@ -14,6 +14,8 @@ import { Errors } from "../../errors.js"; // Types // ============================================================================= +/** Boost entity from the Basecamp API. */ +export type Boost = components["schemas"]["Boost"]; /** * Options for listForRecording. @@ -56,7 +58,7 @@ export class BoostsService extends BaseService { /** * Get a single boost * @param boostId - The boost ID - * @returns The boost + * @returns The Boost * @throws {BasecampError} If the resource is not found * * @example @@ -64,7 +66,7 @@ export class BoostsService extends BaseService { * const result = await client.boosts.get(123); * ``` */ - async get(boostId: number): Promise { + async get(boostId: number): Promise { const response = await this.request( { service: "Boosts", @@ -116,14 +118,14 @@ export class BoostsService extends BaseService { * List boosts on a recording * @param recordingId - The recording ID * @param options - Optional query parameters - * @returns All results across all pages, with .meta.totalCount + * @returns All Boost across all pages, with .meta.totalCount * * @example * ```ts * const result = await client.boosts.listForRecording(123); * ``` */ - async listForRecording(recordingId: number, options?: ListForRecordingBoostOptions): Promise { + async listForRecording(recordingId: number, options?: ListForRecordingBoostOptions): Promise> { return this.requestPaginated( { service: "Boosts", @@ -146,7 +148,7 @@ export class BoostsService extends BaseService { * Create a boost on a recording * @param recordingId - The recording ID * @param req - Recording_boost creation parameters - * @returns The recording_boost + * @returns The Boost * @throws {BasecampError} If required fields are missing or invalid * * @example @@ -154,7 +156,7 @@ export class BoostsService extends BaseService { * const result = await client.boosts.createForRecording(123, { content: "Hello world" }); * ``` */ - async createForRecording(recordingId: number, req: CreateForRecordingBoostRequest): Promise { + async createForRecording(recordingId: number, req: CreateForRecordingBoostRequest): Promise { if (!req.content) { throw Errors.validation("Content is required"); } @@ -184,14 +186,14 @@ export class BoostsService extends BaseService { * @param recordingId - The recording ID * @param eventId - The event ID * @param options - Optional query parameters - * @returns All results across all pages, with .meta.totalCount + * @returns All Boost across all pages, with .meta.totalCount * * @example * ```ts * const result = await client.boosts.listForEvent(123, 123); * ``` */ - async listForEvent(recordingId: number, eventId: number, options?: ListForEventBoostOptions): Promise { + async listForEvent(recordingId: number, eventId: number, options?: ListForEventBoostOptions): Promise> { return this.requestPaginated( { service: "Boosts", @@ -215,7 +217,7 @@ export class BoostsService extends BaseService { * @param recordingId - The recording ID * @param eventId - The event ID * @param req - Event_boost creation parameters - * @returns The event_boost + * @returns The Boost * @throws {BasecampError} If required fields are missing or invalid * * @example @@ -223,7 +225,7 @@ export class BoostsService extends BaseService { * const result = await client.boosts.createForEvent(123, 123, { content: "Hello world" }); * ``` */ - async createForEvent(recordingId: number, eventId: number, req: CreateForEventBoostRequest): Promise { + async createForEvent(recordingId: number, eventId: number, req: CreateForEventBoostRequest): Promise { if (!req.content) { throw Errors.validation("Content is required"); } diff --git a/typescript/src/generated/services/everything.ts b/typescript/src/generated/services/everything.ts new file mode 100644 index 000000000..48754cd68 --- /dev/null +++ b/typescript/src/generated/services/everything.ts @@ -0,0 +1,304 @@ +/** + * Everything service for the Basecamp API. + * + * @generated from OpenAPI spec - do not edit directly + */ + +import { BaseService } from "../../services/base.js"; +import type { components } from "../schema.js"; +import { ListResult } from "../../pagination.js"; +import type { PaginationOptions } from "../../pagination.js"; + +// ============================================================================= +// Types +// ============================================================================= + +/** Boost entity from the Basecamp API. */ +export type Boost = components["schemas"]["Boost"]; +/** Card entity from the Basecamp API. */ +export type Card = components["schemas"]["Card"]; +/** Recording entity from the Basecamp API. */ +export type Recording = components["schemas"]["Recording"]; +/** EverythingFile entity from the Basecamp API. */ +export type EverythingFile = components["schemas"]["EverythingFile"]; +/** Todo entity from the Basecamp API. */ +export type Todo = components["schemas"]["Todo"]; + +/** + * Options for everythingBoosts. + */ +export interface EverythingBoostsEverythingOptions extends PaginationOptions { + /** Page number for paginating through results. Defaults to 1. */ + page?: number; +} + +/** + * Options for everythingCheckins. + */ +export interface EverythingCheckinsEverythingOptions extends PaginationOptions { + /** Page number for paginating through results. Defaults to 1. */ + page?: number; +} + +/** + * Options for everythingComments. + */ +export interface EverythingCommentsEverythingOptions extends PaginationOptions { + /** Page number for paginating through results. Defaults to 1. */ + page?: number; +} + +/** + * Options for everythingFiles. + */ +export interface EverythingFilesEverythingOptions extends PaginationOptions { + /** Filter by file kind: all (default), images, pdfs, documents, or videos. */ + kind?: string; + /** Restrict to files created by the given people (repeatable). */ + peopleIds?: number[]; + /** Page number for paginating through results. Defaults to 1. */ + page?: number; +} + +/** + * Options for everythingForwards. + */ +export interface EverythingForwardsEverythingOptions extends PaginationOptions { + /** Page number for paginating through results. Defaults to 1. */ + page?: number; +} + +/** + * Options for everythingMessages. + */ +export interface EverythingMessagesEverythingOptions extends PaginationOptions { + /** Page number for paginating through results. Defaults to 1. */ + page?: number; +} + + +// ============================================================================= +// Service +// ============================================================================= + +/** + * Service for Everything operations. + */ +export class EverythingService extends BaseService { + + /** + * Get every boost across all accessible projects, newest-first (paginated). + * @param options - Optional query parameters + * @returns All Boost across all pages, with .meta.totalCount + * + * @example + * ```ts + * const result = await client.everything.everythingBoosts(); + * ``` + */ + async everythingBoosts(options?: EverythingBoostsEverythingOptions): Promise> { + return this.requestPaginated( + { + service: "Everything", + operation: "GetEverythingBoosts", + resourceType: "everything_boost", + isMutation: false, + }, + () => + this.client.GET("/boosts.json", { + params: { + query: { page: options?.page }, + }, + }) + , options + ); + } + + /** + * Get every overdue card across all accessible projects, oldest-due-date-first. + * @returns Array of Card + * + * @example + * ```ts + * const result = await client.everything.everythingOverdueCards(); + * ``` + */ + async everythingOverdueCards(): Promise { + const response = await this.request( + { + service: "Everything", + operation: "GetEverythingOverdueCards", + resourceType: "everything_overdue_card", + isMutation: false, + }, + () => + this.client.GET("/cards/overdue.json", { + }) + ); + return response ?? []; + } + + /** + * Get every automatic check-in answer across all accessible projects, + * @param options - Optional query parameters + * @returns All Recording across all pages, with .meta.totalCount + * + * @example + * ```ts + * const result = await client.everything.everythingCheckins(); + * ``` + */ + async everythingCheckins(options?: EverythingCheckinsEverythingOptions): Promise> { + return this.requestPaginated( + { + service: "Everything", + operation: "GetEverythingCheckins", + resourceType: "everything_checkin", + isMutation: false, + }, + () => + this.client.GET("/checkins.json", { + params: { + query: { page: options?.page }, + }, + }) + , options + ); + } + + /** + * Get every comment across all accessible projects, newest-first (paginated). + * @param options - Optional query parameters + * @returns All Recording across all pages, with .meta.totalCount + * + * @example + * ```ts + * const result = await client.everything.everythingComments(); + * ``` + */ + async everythingComments(options?: EverythingCommentsEverythingOptions): Promise> { + return this.requestPaginated( + { + service: "Everything", + operation: "GetEverythingComments", + resourceType: "everything_comment", + isMutation: false, + }, + () => + this.client.GET("/comments.json", { + params: { + query: { page: options?.page }, + }, + }) + , options + ); + } + + /** + * Get every file recording across all accessible projects, newest-first + * @param options - Optional query parameters + * @returns All EverythingFile across all pages, with .meta.totalCount + * + * @example + * ```ts + * const result = await client.everything.everythingFiles(); + * ``` + */ + async everythingFiles(options?: EverythingFilesEverythingOptions): Promise> { + return this.requestPaginated( + { + service: "Everything", + operation: "GetEverythingFiles", + resourceType: "everything_file", + isMutation: false, + }, + () => + this.client.GET("/files.json", { + params: { + query: { kind: options?.kind, "people_ids[]": options?.peopleIds, page: options?.page }, + }, + }) + , options + ); + } + + /** + * Get every inbox forward across all accessible projects, newest-first + * @param options - Optional query parameters + * @returns All Recording across all pages, with .meta.totalCount + * + * @example + * ```ts + * const result = await client.everything.everythingForwards(); + * ``` + */ + async everythingForwards(options?: EverythingForwardsEverythingOptions): Promise> { + return this.requestPaginated( + { + service: "Everything", + operation: "GetEverythingForwards", + resourceType: "everything_forward", + isMutation: false, + }, + () => + this.client.GET("/forwards.json", { + params: { + query: { page: options?.page }, + }, + }) + , options + ); + } + + /** + * Get every message across all accessible projects, newest-first (paginated). + * @param options - Optional query parameters + * @returns All Recording across all pages, with .meta.totalCount + * + * @example + * ```ts + * const result = await client.everything.everythingMessages(); + * ``` + */ + async everythingMessages(options?: EverythingMessagesEverythingOptions): Promise> { + return this.requestPaginated( + { + service: "Everything", + operation: "GetEverythingMessages", + resourceType: "everything_message", + isMutation: false, + }, + () => + this.client.GET("/messages.json", { + params: { + query: { page: options?.page }, + }, + }) + , options + ); + } + + /** + * Get every overdue to-do across all accessible projects, oldest-due-date-first. + * @returns Array of Todo + * + * @example + * ```ts + * const result = await client.everything.everythingOverdueTodos(); + * ``` + */ + async everythingOverdueTodos(): Promise { + const response = await this.request( + { + service: "Everything", + operation: "GetEverythingOverdueTodos", + resourceType: "everything_overdue_todo", + isMutation: false, + }, + () => + this.client.GET("/todos/overdue.json", { + }) + ); + return response ?? []; + } +} \ No newline at end of file diff --git a/typescript/src/generated/services/index.ts b/typescript/src/generated/services/index.ts index 781347443..00ff817dc 100644 --- a/typescript/src/generated/services/index.ts +++ b/typescript/src/generated/services/index.ts @@ -1,5 +1,6 @@ export { AccountService } from "./account.js"; export { AttachmentsService } from "./attachments.js"; +export { EverythingService } from "./everything.js"; export { BoostsService } from "./boosts.js"; export { CardColumnsService } from "./card-columns.js"; export { WormholesService } from "./wormholes.js"; diff --git a/typescript/src/index.ts b/typescript/src/index.ts index 78ab02beb..6b07fbcec 100644 --- a/typescript/src/index.ts +++ b/typescript/src/index.ts @@ -347,6 +347,11 @@ export { TimelineService, } from "./generated/services/timeline.js"; +// Everything aggregates service - generated +export { + EverythingService, +} from "./generated/services/everything.js"; + // Search & Reports services - generated export { SearchService, diff --git a/typescript/tests/services/everything.test.ts b/typescript/tests/services/everything.test.ts new file mode 100644 index 000000000..6b4a88941 --- /dev/null +++ b/typescript/tests/services/everything.test.ts @@ -0,0 +1,334 @@ +/** + * Tests for the EverythingService (generated from OpenAPI spec) + */ +import { describe, it, expect, beforeEach } from "vitest"; +import { http, HttpResponse } from "msw"; +import { server } from "../setup.js"; +import { createBasecampClient } from "../../src/client.js"; +import type { BasecampClient } from "../../src/client.js"; + +const BASE_URL = "https://3.basecampapi.com/12345"; + +describe("EverythingService", () => { + let client: BasecampClient; + + beforeEach(() => { + client = createBasecampClient({ + accountId: "12345", + accessToken: "test-token", + enableRetry: false, + }); + }); + + describe("everythingFiles", () => { + it("should decode the heterogeneous /files.json feed: Upload, Document, and Attachment variants in one array", async () => { + const fixture = [ + { + id: 900, + type: "Upload", + status: "active", + visible_to_clients: false, + title: "logo.png", + inherits_status: true, + filename: "logo.png", + content_type: "image/png", + byte_size: 1281, + width: 1024.0, + height: 768.0, + url: "https://3.basecampapi.com/1/buckets/2/uploads/900.json", + app_url: "https://3.basecamp.com/1/buckets/2/uploads/900", + download_url: "https://3.basecampapi.com/1/buckets/2/uploads/900/download/logo.png", + app_download_url: "https://storage.3.basecamp.com/1/buckets/2/uploads/900/download/logo.png", + bucket: { id: 2, name: "The Leto Laptop", type: "Project" }, + creator: { id: 1, name: "Victor Cooper" }, + }, + { + id: 901, + type: "Document", + status: "active", + visible_to_clients: false, + title: "Spec", + inherits_status: true, + content_type: "text/html", + url: "https://3.basecampapi.com/1/buckets/2/documents/901.json", + app_url: "https://3.basecamp.com/1/buckets/2/documents/901", + bucket: { id: 2, name: "The Leto Laptop", type: "Project" }, + creator: { id: 1, name: "Victor Cooper" }, + }, + { + id: 902, + type: "Attachment", + attachable_sgid: "sgid-902", + filename: "chart.avif", + content_type: "image/avif", + byte_size: 4096, + width: null, + height: null, + download_url: "https://storage.3.basecamp.com/1/blobs/902/download/chart.avif", + parent: { id: 800, title: "A message", type: "Message" }, + }, + ]; + + server.use( + http.get(`${BASE_URL}/files.json`, () => { + return HttpResponse.json(fixture); + }) + ); + + const result = (await client.everything.everythingFiles()) as any[]; + expect(result).toHaveLength(3); + + // variant 1: full Upload recording + expect(result[0].type).toBe("Upload"); + expect(result[0].filename).toBe("logo.png"); + expect(result[0].app_download_url).toBe( + "https://storage.3.basecamp.com/1/buckets/2/uploads/900/download/logo.png" + ); + expect(result[0].width).toBe(1024); + + // variant 2: Basecamp Document recording + expect(result[1].type).toBe("Document"); + expect(result[1].title).toBe("Spec"); + + // variant 3: rich-text Attachment envelope + expect(result[2].type).toBe("Attachment"); + expect(result[2].attachable_sgid).toBe("sgid-902"); + expect(result[2].parent).toBeDefined(); + expect(result[2].width).toBeNull(); + }); + }); + + describe("everythingMessages", () => { + it("should decode the paginated /messages.json Recording feed with embedded buckets", async () => { + const fixture = [ + { + id: 1001, + type: "Message", + title: "Kickoff", + bucket: { id: 2, name: "The Leto Laptop", type: "Project" }, + creator: { id: 1, name: "Victor Cooper" }, + }, + { + id: 1002, + type: "Message", + title: "Status update", + bucket: { id: 3, name: "Honcho Rollout", type: "Project" }, + creator: { id: 1, name: "Victor Cooper" }, + }, + ]; + + server.use( + http.get(`${BASE_URL}/messages.json`, () => { + return HttpResponse.json(fixture); + }) + ); + + const result = (await client.everything.everythingMessages()) as any[]; + expect(result).toHaveLength(2); + expect(result[0].id).toBe(1001); + expect(result[0].bucket).toEqual({ id: 2, name: "The Leto Laptop", type: "Project" }); + expect(result[1].id).toBe(1002); + expect(result[1].bucket.id).toBe(3); + }); + }); + + describe("everythingComments", () => { + it("should decode the /comments.json Recording feed with embedded buckets", async () => { + const fixture = [ + { + id: 2001, + type: "Comment", + bucket: { id: 2, name: "The Leto Laptop", type: "Project" }, + creator: { id: 1, name: "Victor Cooper" }, + }, + { + id: 2002, + type: "Comment", + bucket: { id: 2, name: "The Leto Laptop", type: "Project" }, + creator: { id: 5, name: "Annie Bryan" }, + }, + ]; + + server.use( + http.get(`${BASE_URL}/comments.json`, () => { + return HttpResponse.json(fixture); + }) + ); + + const result = (await client.everything.everythingComments()) as any[]; + expect(result).toHaveLength(2); + expect(result[0].id).toBe(2001); + expect(result[0].bucket).toEqual({ id: 2, name: "The Leto Laptop", type: "Project" }); + expect(result[1].id).toBe(2002); + }); + }); + + describe("everythingCheckins", () => { + it("should decode the /checkins.json Recording feed with embedded buckets", async () => { + const fixture = [ + { + id: 3001, + type: "Question::Answer", + bucket: { id: 2, name: "The Leto Laptop", type: "Project" }, + creator: { id: 1, name: "Victor Cooper" }, + }, + { + id: 3002, + type: "Question::Answer", + bucket: { id: 4, name: "Marketing Site", type: "Project" }, + creator: { id: 5, name: "Annie Bryan" }, + }, + ]; + + server.use( + http.get(`${BASE_URL}/checkins.json`, () => { + return HttpResponse.json(fixture); + }) + ); + + const result = (await client.everything.everythingCheckins()) as any[]; + expect(result).toHaveLength(2); + expect(result[0].id).toBe(3001); + expect(result[0].bucket).toEqual({ id: 2, name: "The Leto Laptop", type: "Project" }); + expect(result[1].bucket.id).toBe(4); + }); + }); + + describe("everythingForwards", () => { + it("should decode the /forwards.json Recording feed with embedded buckets", async () => { + const fixture = [ + { + id: 4001, + type: "Inbox::Forward", + bucket: { id: 2, name: "The Leto Laptop", type: "Project" }, + creator: { id: 1, name: "Victor Cooper" }, + }, + { + id: 4002, + type: "Inbox::Forward", + bucket: { id: 2, name: "The Leto Laptop", type: "Project" }, + creator: { id: 5, name: "Annie Bryan" }, + }, + ]; + + server.use( + http.get(`${BASE_URL}/forwards.json`, () => { + return HttpResponse.json(fixture); + }) + ); + + const result = (await client.everything.everythingForwards()) as any[]; + expect(result).toHaveLength(2); + expect(result[0].id).toBe(4001); + expect(result[0].bucket).toEqual({ id: 2, name: "The Leto Laptop", type: "Project" }); + expect(result[1].id).toBe(4002); + }); + }); + + describe("everythingBoosts", () => { + it("should decode the /boosts.json feed with booster and nested recording", async () => { + const fixture = [ + { + id: 5001, + content: "👏", + created_at: "2024-01-15T10:00:00Z", + booster: { id: 1, name: "Victor Cooper" }, + recording: { id: 800, title: "A message", type: "Message" }, + }, + { + id: 5002, + content: "🔥", + created_at: "2024-01-15T11:00:00Z", + booster: { id: 5, name: "Annie Bryan" }, + recording: { id: 801, title: "A comment", type: "Comment" }, + }, + ]; + + server.use( + http.get(`${BASE_URL}/boosts.json`, () => { + return HttpResponse.json(fixture); + }) + ); + + const result = (await client.everything.everythingBoosts()) as any[]; + expect(result).toHaveLength(2); + expect(result[0].id).toBe(5001); + expect(result[0].booster).toEqual({ id: 1, name: "Victor Cooper" }); + expect(result[0].recording).toEqual({ id: 800, title: "A message", type: "Message" }); + expect(result[1].id).toBe(5002); + expect(result[1].recording.id).toBe(801); + }); + }); + + describe("everythingOverdueTodos", () => { + it("should decode the unpaginated /todos/overdue.json array, oldest-first", async () => { + const fixture = [ + { + id: 6001, + type: "Todo", + title: "Ship the thing", + due_on: "2024-01-10", + bucket: { id: 2, name: "The Leto Laptop", type: "Project" }, + }, + { + id: 6002, + type: "Todo", + title: "Review the docs", + due_on: "2024-01-20", + bucket: { id: 2, name: "The Leto Laptop", type: "Project" }, + }, + ]; + + server.use( + http.get(`${BASE_URL}/todos/overdue.json`, () => { + return HttpResponse.json(fixture); + }) + ); + + const result = (await client.everything.everythingOverdueTodos()) as any[]; + expect(result).toHaveLength(2); + expect(result[0].id).toBe(6001); + expect(result[1].id).toBe(6002); + // oldest-first ordering by due_on + expect(result[0].due_on).toBe("2024-01-10"); + expect(result[1].due_on).toBe("2024-01-20"); + expect(result[0].due_on! < result[1].due_on!).toBe(true); + }); + }); + + describe("everythingOverdueCards", () => { + it("should decode the unpaginated /cards/overdue.json array, oldest-first", async () => { + const fixture = [ + { + id: 7001, + type: "Kanban::Card", + title: "Draft proposal", + due_on: "2024-02-01", + bucket: { id: 2, name: "The Leto Laptop", type: "Project" }, + }, + { + id: 7002, + type: "Kanban::Card", + title: "Send invoice", + due_on: "2024-02-14", + bucket: { id: 2, name: "The Leto Laptop", type: "Project" }, + }, + ]; + + server.use( + http.get(`${BASE_URL}/cards/overdue.json`, () => { + return HttpResponse.json(fixture); + }) + ); + + const result = (await client.everything.everythingOverdueCards()) as any[]; + expect(result).toHaveLength(2); + expect(result[0].id).toBe(7001); + expect(result[1].id).toBe(7002); + // oldest-first ordering by due_on + expect(result[0].due_on).toBe("2024-02-01"); + expect(result[1].due_on).toBe("2024-02-14"); + expect(result[0].due_on! < result[1].due_on!).toBe(true); + }); + }); +}); From 588cc41c278f9be2d524f46f06d68664defc97d5 Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Sat, 25 Jul 2026 13:37:34 -0700 Subject: [PATCH 2/5] Fetch unpaginated everything overdue feeds without Link-following (Python) The overdue todo/card feeds are complete, unpaginated arrays. The Python generator was routing every bare-array response through _request_paginated (Link-following) after the $ref return-type fix; a Link header (or a proxy that injects one) would have made the SDK walk pages the contract says do not exist. Add a _request_list base helper (sync + async) that issues a single GET and wraps the full array, and split the generator so only x-basecamp-pagination operations follow Link headers; bare arrays without that marker use the single request. Matches the plain full-array decode Go/TS/Ruby/Kotlin already use. Also corrects the five pre-existing unpaginated arrays (assignments, timesheet report, assignable people, lineup markers). --- python/scripts/generate_services.py | 22 ++++++++++++-- .../generated/services/_async_base.py | 30 +++++++++++++++++++ .../src/basecamp/generated/services/_base.py | 30 +++++++++++++++++++ .../basecamp/generated/services/automation.py | 4 +-- .../basecamp/generated/services/everything.py | 8 ++--- .../generated/services/my_assignments.py | 8 ++--- .../src/basecamp/generated/services/people.py | 4 +-- .../basecamp/generated/services/timesheets.py | 4 +-- python/tests/services/test_everything.py | 20 +++++++++++++ 9 files changed, 114 insertions(+), 16 deletions(-) diff --git a/python/scripts/generate_services.py b/python/scripts/generate_services.py index 0b573324a..ece97e624 100644 --- a/python/scripts/generate_services.py +++ b/python/scripts/generate_services.py @@ -653,7 +653,17 @@ def operation_kwarg(op: dict) -> str: def is_paginated_list(op: dict) -> bool: - return (op["returns_array"] or op["has_pagination"]) and not op["pagination_key"] + # Link-header paginated: the operation is explicitly marked paginated. A bare + # array without that marker is a complete, unpaginated collection (see + # is_unpaginated_array) and must NOT follow Link headers. + return op["has_pagination"] and not op["pagination_key"] + + +def is_unpaginated_array(op: dict) -> bool: + # Returns a bare array but is not marked paginated: the whole collection comes + # back in a single response (e.g. the overdue todo/card feeds). Fetched with a + # single request, no Link-following — matching the other SDKs' plain decode. + return op["returns_array"] and not op["has_pagination"] and not op["pagination_key"] def is_wrapped_paginated(op: dict) -> bool: @@ -665,7 +675,7 @@ def return_type(op: dict) -> str: return "None" if is_wrapped_paginated(op): return "dict[str, Any]" - if is_paginated_list(op): + if is_paginated_list(op) or is_unpaginated_array(op): return "ListResult" return "dict[str, Any]" @@ -693,6 +703,14 @@ def generate_method_body(op: dict, service_name: str, *, is_async: bool) -> list lines.append(" )") else: lines.append(f" return {_await(is_async)}self._request_paginated(OperationInfo({info_kwargs}), {path_expr})") + elif is_unpaginated_array(op): + if op["query_params"]: + lines.append(f" return {_await(is_async)}self._request_list(") + lines.append(f" OperationInfo({info_kwargs}), {path_expr},") + lines.append(f" params={build_query_params_expr(op)},") + lines.append(" )") + else: + lines.append(f" return {_await(is_async)}self._request_list(OperationInfo({info_kwargs}), {path_expr})") elif op["has_binary_body"]: # Binary upload if op["query_params"]: diff --git a/python/src/basecamp/generated/services/_async_base.py b/python/src/basecamp/generated/services/_async_base.py index 3f825141d..dac6bddbc 100644 --- a/python/src/basecamp/generated/services/_async_base.py +++ b/python/src/basecamp/generated/services/_async_base.py @@ -75,6 +75,36 @@ async def _request( safe_hook(self._hooks.on_operation_end, info, OperationResult(duration_ms=duration_ms, error=e)) raise + async def _request_list( + self, + info: OperationInfo, + path: str, + *, + params: dict | None = None, + ) -> ListResult: + """Fetch a complete, unpaginated array in a single request. + + Unlike ``_request_paginated`` this never follows ``Link`` headers: the + endpoint returns the whole collection at once (e.g. the overdue todo/card + feeds, sorted oldest-first). Matches the plain full-array decode the other + SDKs use for these routes. + """ + start = time.monotonic() + safe_hook(self._hooks.on_operation_start, info) + try: + response = await self._client.http.get(self._client.account_path(path), params=params) + _security.check_body_size(response.content, _security.MAX_RESPONSE_BODY_BYTES) + items = response.json() + _normalize_person_ids(items) + total_count = parse_total_count(dict(response.headers)) + duration_ms = int((time.monotonic() - start) * 1000) + safe_hook(self._hooks.on_operation_end, info, OperationResult(duration_ms=duration_ms)) + return ListResult(items, ListMeta(total_count=total_count, truncated=False)) + except Exception as e: + duration_ms = int((time.monotonic() - start) * 1000) + safe_hook(self._hooks.on_operation_end, info, OperationResult(duration_ms=duration_ms, error=e)) + raise + async def _request_void( self, info: OperationInfo, diff --git a/python/src/basecamp/generated/services/_base.py b/python/src/basecamp/generated/services/_base.py index d5aa6a853..9ca799735 100644 --- a/python/src/basecamp/generated/services/_base.py +++ b/python/src/basecamp/generated/services/_base.py @@ -82,6 +82,36 @@ def _request( safe_hook(self._hooks.on_operation_end, info, OperationResult(duration_ms=duration_ms, error=e)) raise + def _request_list( + self, + info: OperationInfo, + path: str, + *, + params: dict | None = None, + ) -> ListResult: + """Fetch a complete, unpaginated array in a single request. + + Unlike ``_request_paginated`` this never follows ``Link`` headers: the + endpoint returns the whole collection at once (e.g. the overdue todo/card + feeds, sorted oldest-first). Matches the plain full-array decode the other + SDKs use for these routes. + """ + start = time.monotonic() + safe_hook(self._hooks.on_operation_start, info) + try: + response = self._client.http.get(self._client.account_path(path), params=params) + _security.check_body_size(response.content, _security.MAX_RESPONSE_BODY_BYTES) + items = response.json() + _normalize_person_ids(items) + total_count = parse_total_count(dict(response.headers)) + duration_ms = int((time.monotonic() - start) * 1000) + safe_hook(self._hooks.on_operation_end, info, OperationResult(duration_ms=duration_ms)) + return ListResult(items, ListMeta(total_count=total_count, truncated=False)) + except Exception as e: + duration_ms = int((time.monotonic() - start) * 1000) + safe_hook(self._hooks.on_operation_end, info, OperationResult(duration_ms=duration_ms, error=e)) + raise + def _request_void( self, info: OperationInfo, diff --git a/python/src/basecamp/generated/services/automation.py b/python/src/basecamp/generated/services/automation.py index 392d668f4..e8a8991ce 100644 --- a/python/src/basecamp/generated/services/automation.py +++ b/python/src/basecamp/generated/services/automation.py @@ -12,7 +12,7 @@ class AutomationService(BaseService): def list_lineup_markers(self) -> ListResult: - return self._request_paginated( + return self._request_list( OperationInfo(service="automation", operation="list_lineup_markers", is_mutation=False), "/lineup/markers.json", ) @@ -20,7 +20,7 @@ def list_lineup_markers(self) -> ListResult: class AsyncAutomationService(AsyncBaseService): async def list_lineup_markers(self) -> ListResult: - return await self._request_paginated( + return await self._request_list( OperationInfo(service="automation", operation="list_lineup_markers", is_mutation=False), "/lineup/markers.json", ) diff --git a/python/src/basecamp/generated/services/everything.py b/python/src/basecamp/generated/services/everything.py index eaa9b70fa..d2fee438b 100644 --- a/python/src/basecamp/generated/services/everything.py +++ b/python/src/basecamp/generated/services/everything.py @@ -19,7 +19,7 @@ def get_everything_boosts(self, *, page: int | None = None) -> ListResult: ) def get_everything_overdue_cards(self) -> ListResult: - return self._request_paginated( + return self._request_list( OperationInfo(service="everything", operation="get_everything_overdue_cards", is_mutation=False), "/cards/overdue.json", ) @@ -62,7 +62,7 @@ def get_everything_messages(self, *, page: int | None = None) -> ListResult: ) def get_everything_overdue_todos(self) -> ListResult: - return self._request_paginated( + return self._request_list( OperationInfo(service="everything", operation="get_everything_overdue_todos", is_mutation=False), "/todos/overdue.json", ) @@ -77,7 +77,7 @@ async def get_everything_boosts(self, *, page: int | None = None) -> ListResult: ) async def get_everything_overdue_cards(self) -> ListResult: - return await self._request_paginated( + return await self._request_list( OperationInfo(service="everything", operation="get_everything_overdue_cards", is_mutation=False), "/cards/overdue.json", ) @@ -120,7 +120,7 @@ async def get_everything_messages(self, *, page: int | None = None) -> ListResul ) async def get_everything_overdue_todos(self) -> ListResult: - return await self._request_paginated( + return await self._request_list( OperationInfo(service="everything", operation="get_everything_overdue_todos", is_mutation=False), "/todos/overdue.json", ) diff --git a/python/src/basecamp/generated/services/my_assignments.py b/python/src/basecamp/generated/services/my_assignments.py index b90ef8d9c..16377a596 100644 --- a/python/src/basecamp/generated/services/my_assignments.py +++ b/python/src/basecamp/generated/services/my_assignments.py @@ -19,13 +19,13 @@ def get_my_assignments(self) -> dict[str, Any]: ) def get_my_completed_assignments(self) -> ListResult: - return self._request_paginated( + return self._request_list( OperationInfo(service="myassignments", operation="get_my_completed_assignments", is_mutation=False), "/my/assignments/completed.json", ) def get_my_due_assignments(self, *, scope: str | None = None) -> ListResult: - return self._request_paginated( + return self._request_list( OperationInfo(service="myassignments", operation="get_my_due_assignments", is_mutation=False), "/my/assignments/due.json", params=self._compact(scope=scope), @@ -41,13 +41,13 @@ async def get_my_assignments(self) -> dict[str, Any]: ) async def get_my_completed_assignments(self) -> ListResult: - return await self._request_paginated( + return await self._request_list( OperationInfo(service="myassignments", operation="get_my_completed_assignments", is_mutation=False), "/my/assignments/completed.json", ) async def get_my_due_assignments(self, *, scope: str | None = None) -> ListResult: - return await self._request_paginated( + return await self._request_list( OperationInfo(service="myassignments", operation="get_my_due_assignments", is_mutation=False), "/my/assignments/due.json", params=self._compact(scope=scope), diff --git a/python/src/basecamp/generated/services/people.py b/python/src/basecamp/generated/services/people.py index 7df9595fa..2ff534f98 100644 --- a/python/src/basecamp/generated/services/people.py +++ b/python/src/basecamp/generated/services/people.py @@ -125,7 +125,7 @@ def update_project_access( ) def list_assignable(self) -> ListResult: - return self._request_paginated( + return self._request_list( OperationInfo(service="people", operation="list_assignable", is_mutation=False), "/reports/todos/assigned.json", ) @@ -246,7 +246,7 @@ async def update_project_access( ) async def list_assignable(self) -> ListResult: - return await self._request_paginated( + return await self._request_list( OperationInfo(service="people", operation="list_assignable", is_mutation=False), "/reports/todos/assigned.json", ) diff --git a/python/src/basecamp/generated/services/timesheets.py b/python/src/basecamp/generated/services/timesheets.py index 35e754500..2700dd28b 100644 --- a/python/src/basecamp/generated/services/timesheets.py +++ b/python/src/basecamp/generated/services/timesheets.py @@ -41,7 +41,7 @@ def create( ) def report(self, *, from_: str | None = None, to: str | None = None, person_id: int | None = None) -> ListResult: - return self._request_paginated( + return self._request_list( OperationInfo(service="timesheets", operation="report", is_mutation=False), "/reports/timesheet.json", params={k: v for k, v in {"from": from_, "to": to, "person_id": person_id}.items() if v is not None}, @@ -105,7 +105,7 @@ async def create( async def report( self, *, from_: str | None = None, to: str | None = None, person_id: int | None = None ) -> ListResult: - return await self._request_paginated( + return await self._request_list( OperationInfo(service="timesheets", operation="report", is_mutation=False), "/reports/timesheet.json", params={k: v for k, v in {"from": from_, "to": to, "person_id": person_id}.items() if v is not None}, diff --git a/python/tests/services/test_everything.py b/python/tests/services/test_everything.py index eeffe4ab5..2385b0014 100644 --- a/python/tests/services/test_everything.py +++ b/python/tests/services/test_everything.py @@ -350,3 +350,23 @@ def test_overdue_cards_returns_oldest_first_bare_array(self): assert result[0]["type"] == "Kanban::Card" assert result[0]["due_on"] == "2024-02-01" assert result[0]["due_on"] < result[1]["due_on"] + + @respx.mock + def test_overdue_todos_does_not_follow_link_header(self): + # The overdue feeds are complete, unpaginated arrays. Even if the server + # advertises a next page via a Link header, the SDK must issue exactly one + # request and return only that response — matching the other SDKs' plain + # full-array decode (no Link-following). + route = respx.get("https://3.basecampapi.com/12345/todos/overdue.json").mock( + return_value=httpx.Response( + 200, + json=_OVERDUE_TODOS_FEED, + headers={"Link": '; rel="next"'}, + ) + ) + + account = Client(access_token="test-token").for_account("12345") + result = account.everything.get_everything_overdue_todos() + + assert route.call_count == 1 + assert len(result) == 2 From 3e4c39ed7e1ae0dc018ff068b9f8a4f53c029f31 Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Sat, 25 Jul 2026 13:51:44 -0700 Subject: [PATCH 3/5] Add flat-family everything error-path tests across TS/Ruby/Python Per the SDK Change Completeness Bar, every new operation needs an error case alongside its happy path. Add parameterized 4xx propagation coverage (404 -> BasecampError/NotFoundError) for all eight flat-family operations in each validating SDK. --- python/tests/services/test_everything.py | 28 +++++++++++++++++++ .../services/everything_service_test.rb | 21 ++++++++++++++ typescript/tests/services/everything.test.ts | 22 +++++++++++++++ 3 files changed, 71 insertions(+) diff --git a/python/tests/services/test_everything.py b/python/tests/services/test_everything.py index 2385b0014..e3739f67d 100644 --- a/python/tests/services/test_everything.py +++ b/python/tests/services/test_everything.py @@ -3,9 +3,11 @@ from __future__ import annotations import httpx +import pytest import respx from basecamp import Client +from basecamp.errors import NotFoundError # Canonical heterogeneous feed: a full Upload recording, a Basecamp Document # recording, and a rich-text Attachment envelope in a single non-empty array. @@ -370,3 +372,29 @@ def test_overdue_todos_does_not_follow_link_header(self): assert route.call_count == 1 assert len(result) == 2 + + +# Every flat-family operation must surface a canonical 4xx as a typed error. +_FLAT_ERROR_CASES = [ + ("messages.json", lambda acct: acct.everything.get_everything_messages()), + ("comments.json", lambda acct: acct.everything.get_everything_comments()), + ("checkins.json", lambda acct: acct.everything.get_everything_checkins()), + ("forwards.json", lambda acct: acct.everything.get_everything_forwards()), + ("boosts.json", lambda acct: acct.everything.get_everything_boosts()), + ("files.json", lambda acct: acct.everything.get_everything_files(kind=None, people_ids=None)), + ("todos/overdue.json", lambda acct: acct.everything.get_everything_overdue_todos()), + ("cards/overdue.json", lambda acct: acct.everything.get_everything_overdue_cards()), +] + + +class TestEverythingErrorPropagation: + @pytest.mark.parametrize("path, call", _FLAT_ERROR_CASES) + @respx.mock + def test_flat_family_propagates_not_found(self, path, call): + respx.get(f"https://3.basecampapi.com/12345/{path}").mock( + return_value=httpx.Response(404, json={"error": "Not found"}) + ) + + account = Client(access_token="test-token").for_account("12345") + with pytest.raises(NotFoundError): + call(account) diff --git a/ruby/test/basecamp/services/everything_service_test.rb b/ruby/test/basecamp/services/everything_service_test.rb index 794cc702e..ccb40a77a 100644 --- a/ruby/test/basecamp/services/everything_service_test.rb +++ b/ruby/test/basecamp/services/everything_service_test.rb @@ -323,4 +323,25 @@ def test_get_everything_overdue_cards_decodes_oldest_first # Oldest-first ordering assert result[0]["due_on"] < result[1]["due_on"] end + + # Every flat-family operation must surface a canonical 4xx as a typed error. + def test_flat_family_operations_propagate_not_found + calls = { + "/12345/messages.json" => -> { @account.everything.get_everything_messages.to_a }, + "/12345/comments.json" => -> { @account.everything.get_everything_comments.to_a }, + "/12345/checkins.json" => -> { @account.everything.get_everything_checkins.to_a }, + "/12345/forwards.json" => -> { @account.everything.get_everything_forwards.to_a }, + "/12345/boosts.json" => -> { @account.everything.get_everything_boosts.to_a }, + "/12345/files.json" => -> { @account.everything.get_everything_files(kind: nil, people_ids: nil).to_a }, + "/12345/todos/overdue.json" => -> { @account.everything.get_everything_overdue_todos.to_a }, + "/12345/cards/overdue.json" => -> { @account.everything.get_everything_overdue_cards.to_a } + } + + calls.each do |path, call| + stub_get(path, response_body: "", status: 404) + assert_raises(Basecamp::NotFoundError, "expected #{path} to raise NotFoundError") do + call.call + end + end + end end diff --git a/typescript/tests/services/everything.test.ts b/typescript/tests/services/everything.test.ts index 6b4a88941..32a6e7752 100644 --- a/typescript/tests/services/everything.test.ts +++ b/typescript/tests/services/everything.test.ts @@ -5,6 +5,7 @@ import { describe, it, expect, beforeEach } from "vitest"; import { http, HttpResponse } from "msw"; import { server } from "../setup.js"; import { createBasecampClient } from "../../src/client.js"; +import { BasecampError } from "../../src/errors.js"; import type { BasecampClient } from "../../src/client.js"; const BASE_URL = "https://3.basecampapi.com/12345"; @@ -331,4 +332,25 @@ describe("EverythingService", () => { expect(result[0].due_on! < result[1].due_on!).toBe(true); }); }); + + describe("error propagation", () => { + const cases: Array<[string, string, () => Promise]> = [ + ["everythingMessages", "/messages.json", () => client.everything.everythingMessages()], + ["everythingComments", "/comments.json", () => client.everything.everythingComments()], + ["everythingCheckins", "/checkins.json", () => client.everything.everythingCheckins()], + ["everythingForwards", "/forwards.json", () => client.everything.everythingForwards()], + ["everythingBoosts", "/boosts.json", () => client.everything.everythingBoosts()], + ["everythingFiles", "/files.json", () => client.everything.everythingFiles()], + ["everythingOverdueTodos", "/todos/overdue.json", () => client.everything.everythingOverdueTodos()], + ["everythingOverdueCards", "/cards/overdue.json", () => client.everything.everythingOverdueCards()], + ]; + + it.each(cases)("%s propagates a 4xx as a BasecampError", async (_name, path, call) => { + server.use( + http.get(`${BASE_URL}${path}`, () => HttpResponse.json({ error: "Not found" }, { status: 404 })) + ); + + await expect(call()).rejects.toThrow(BasecampError); + }); + }); }); From 35a609b24bf3e05e340e0dbe166043b121a5a3e2 Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Sat, 25 Jul 2026 14:53:10 -0700 Subject: [PATCH 4/5] Model Document body in EverythingFile, make optional scalars presence-faithful MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses the everything-flat-family re-review: - EverythingFile now models the Document variant's rich-text body (content + content_attachments); typed decoders previously dropped a Document's body. - Go: id/comments_count/boosts_count/position/byte_size become pointers so an absent field decodes to nil instead of a fabricated 0, per SPEC.md §10; the converter carries them through and preserves empty arrays. - Kotlin: the model generator now emits optional integer/boolean/number fields as nullable (was a non-null 0/false sentinel), aligning every model with SPEC.md §10 Optional Fields. Regenerated all models; fixed one test that read a now-nullable Boolean. - TypeScript: re-export EverythingFile and the six *EverythingOptions types from the package root so consumers can name them. - Docs: bump the operation count to 217 (AGENTS.md, SPEC.md). --- AGENTS.md | 4 ++-- SPEC.md | 4 ++-- go/pkg/basecamp/everything.go | 24 ++++++++++++------- go/pkg/basecamp/everything_test.go | 18 +++++++++++++- go/pkg/generated/client.gen.go | 18 ++++++++------ .../basecamp/sdk/generator/ModelEmitter.kt | 11 +++++---- .../basecamp/sdk/generated/models/Answer.kt | 4 ++-- .../basecamp/sdk/generated/models/Campfire.kt | 2 +- .../sdk/generated/models/CampfireLine.kt | 2 +- .../models/CampfireLineAttachment.kt | 2 +- .../com/basecamp/sdk/generated/models/Card.kt | 8 +++---- .../sdk/generated/models/CardColumn.kt | 6 ++--- .../basecamp/sdk/generated/models/CardStep.kt | 4 ++-- .../sdk/generated/models/ClientApproval.kt | 2 +- .../models/ClientApprovalResponse.kt | 8 +++---- .../generated/models/ClientCorrespondence.kt | 2 +- .../basecamp/sdk/generated/models/Comment.kt | 2 +- .../basecamp/sdk/generated/models/DockItem.kt | 2 +- .../basecamp/sdk/generated/models/Document.kt | 6 ++--- .../basecamp/sdk/generated/models/Event.kt | 2 +- .../sdk/generated/models/EverythingFile.kt | 18 +++++++------- .../basecamp/sdk/generated/models/Forward.kt | 2 +- .../sdk/generated/models/ForwardReply.kt | 2 +- .../basecamp/sdk/generated/models/Inbox.kt | 4 ++-- .../basecamp/sdk/generated/models/Message.kt | 4 ++-- .../sdk/generated/models/MessageBoard.kt | 4 ++-- .../sdk/generated/models/Notification.kt | 6 ++--- .../basecamp/sdk/generated/models/Person.kt | 18 +++++++------- .../generated/models/PreviewableAttachment.kt | 8 +++---- .../basecamp/sdk/generated/models/Project.kt | 4 ++-- .../basecamp/sdk/generated/models/Question.kt | 4 ++-- .../sdk/generated/models/QuestionSchedule.kt | 10 ++++---- .../sdk/generated/models/Questionnaire.kt | 2 +- .../sdk/generated/models/Recording.kt | 4 ++-- .../basecamp/sdk/generated/models/Schedule.kt | 6 ++--- .../sdk/generated/models/ScheduleEntry.kt | 6 ++--- .../generated/models/TimelineAttachment.kt | 8 +++---- .../sdk/generated/models/TimelineEvent.kt | 4 ++-- .../sdk/generated/models/TimelineEventData.kt | 2 +- .../com/basecamp/sdk/generated/models/Todo.kt | 8 +++---- .../basecamp/sdk/generated/models/Todolist.kt | 8 +++---- .../sdk/generated/models/TodolistGroup.kt | 6 ++--- .../basecamp/sdk/generated/models/Todoset.kt | 10 ++++---- .../com/basecamp/sdk/generated/models/Tool.kt | 2 +- .../basecamp/sdk/generated/models/Upload.kt | 8 +++---- .../basecamp/sdk/generated/models/Vault.kt | 8 +++---- .../basecamp/sdk/generated/models/Webhook.kt | 2 +- .../sdk/generated/models/WebhookCopy.kt | 2 +- .../sdk/generated/models/WebhookCopyBucket.kt | 2 +- .../sdk/generated/models/WebhookDelivery.kt | 2 +- .../models/WebhookDeliveryResponse.kt | 2 +- .../sdk/generated/models/WebhookEvent.kt | 2 +- .../com/basecamp/sdk/TimelineDecodeTest.kt | 2 +- openapi.json | 22 +++++++++++++---- python/src/basecamp/generated/types.py | 2 ++ ruby/lib/basecamp/generated/types.rb | 8 +++++-- scripts/enhance-openapi-go-types.sh | 14 +++++++---- spec/basecamp.smithy | 4 ++++ .../Generated/Models/EverythingFile.swift | 2 ++ .../src/generated/openapi-stripped.json | 22 +++++++++++++---- typescript/src/generated/schema.d.ts | 3 +++ typescript/src/index.ts | 7 ++++++ 62 files changed, 242 insertions(+), 153 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 986ce4840..0a142d7f5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,7 +4,7 @@ | Component | Status | Details | |-----------|--------|---------| -| **Smithy Spec** | 209 operations | Single source of truth for all APIs | +| **Smithy Spec** | 217 operations | Single source of truth for all APIs | | **Go SDK** | Production-ready | Full generated client + service wrappers | | **TypeScript SDK** | Production-ready | 45 generated services, openapi-fetch based | | **Ruby SDK** | Production-ready | 45 generated services | @@ -31,7 +31,7 @@ Smithy Spec → OpenAPI → Generated Client → Service Layer → User | **Kotlin** | Ktor via `BaseService` | `sdk/src/commonMain/kotlin/.../generated/services/*.kt` | | **Python** | httpx via `HttpClient` | `src/basecamp/generated/services/*.py` | -All 209 operations across the ~45-service per-SDK layer are generated. Hand-written code is limited to infrastructure: +All 217 operations across the ~45-service per-SDK layer are generated. Hand-written code is limited to infrastructure: | Purpose | TypeScript | Ruby | Swift | Kotlin | Python | |---------|-----------|------|-------|--------|--------| diff --git a/SPEC.md b/SPEC.md index 6fa96123d..d294e2b37 100644 --- a/SPEC.md +++ b/SPEC.md @@ -496,7 +496,7 @@ END ### behavior-model.json Retry Patterns -All 209 operations in `behavior-model.json` use `retry_on: [429, 503]`. Three `(max, base_delay_ms)` patterns exist: +All 217 operations in `behavior-model.json` use `retry_on: [429, 503]`. Three `(max, base_delay_ms)` patterns exist: - `(2, 1000)` — most create operations - `(3, 1000)` — most read/update/delete operations - `(3, 2000)` — `CreateAttachment`, `CreateCampfireUpload` (file uploads) @@ -1548,7 +1548,7 @@ Every operation has a `retry` block, including non-idempotent POSTs. For non-ide ### Operation Counts -- Total operations: 209 +- Total operations: 217 - Idempotent: 69 (flagged with `idempotent: true`) - Non-idempotent: 140 (no `idempotent` field, or not present) - All operations use `retry_on: [429, 503]` diff --git a/go/pkg/basecamp/everything.go b/go/pkg/basecamp/everything.go index 89068644e..04b1b179a 100644 --- a/go/pkg/basecamp/everything.go +++ b/go/pkg/basecamp/everything.go @@ -57,7 +57,11 @@ type EverythingFilesOptions struct { // (distinguished by AttachableSGID and blob metadata). Only the fields of the // variant an instance represents are populated. type EverythingFile struct { - ID int64 `json:"id,omitempty"` + // ID and the numeric scalars below are pointers so an absent field (the + // variant this instance is not) stays nil and round-trips as omitted rather + // than a fabricated zero, per SPEC.md §10 (optional fields must preserve + // absence, not substitute a sentinel). + ID *int64 `json:"id,omitempty"` Status string `json:"status,omitempty"` // VisibleToClients/CreatedAt/UpdatedAt/InheritsStatus are pointers so an // absent field (the variant this instance is not) stays nil and round-trips @@ -73,11 +77,11 @@ type EverythingFile struct { AppURL string `json:"app_url,omitempty"` BookmarkURL string `json:"bookmark_url,omitempty"` SubscriptionURL string `json:"subscription_url,omitempty"` - CommentsCount int32 `json:"comments_count,omitempty"` + CommentsCount *int32 `json:"comments_count,omitempty"` CommentsURL string `json:"comments_url,omitempty"` - BoostsCount int32 `json:"boosts_count,omitempty"` + BoostsCount *int32 `json:"boosts_count,omitempty"` BoostsURL string `json:"boosts_url,omitempty"` - Position int32 `json:"position,omitempty"` + Position *int32 `json:"position,omitempty"` Parent *Parent `json:"parent,omitempty"` Bucket *Bucket `json:"bucket,omitempty"` Creator *Person `json:"creator,omitempty"` @@ -85,7 +89,7 @@ type EverythingFile struct { AttachableSGID string `json:"attachable_sgid,omitempty"` // Blob/file metadata (uploads and attachments). ContentType string `json:"content_type,omitempty"` - ByteSize int64 `json:"byte_size,omitempty"` + ByteSize *int64 `json:"byte_size,omitempty"` Filename string `json:"filename,omitempty"` DownloadURL string `json:"download_url,omitempty"` AppDownloadURL string `json:"app_download_url,omitempty"` @@ -97,6 +101,10 @@ type EverythingFile struct { // DescriptionAttachments carries the rich-text companion array for the // upload/document Description (absent on the attachment variant). DescriptionAttachments *[]RichTextAttachment `json:"description_attachments,omitempty"` + // Content and ContentAttachments carry the Document variant's rich-text body + // (uploads and attachments omit them). + Content string `json:"content,omitempty"` + ContentAttachments *[]RichTextAttachment `json:"content_attachments,omitempty"` } // UnmarshalJSON routes decoding through the generated EverythingFile so the @@ -117,6 +125,7 @@ func (f *EverythingFile) UnmarshalJSON(data []byte) error { // the public *int32 nil, and a present value is narrowed to int32. func everythingFileFromGenerated(gf generated.EverythingFile) EverythingFile { f := EverythingFile{ + ID: gf.Id, Status: gf.Status, VisibleToClients: gf.VisibleToClients, CreatedAt: gf.CreatedAt, @@ -140,9 +149,7 @@ func everythingFileFromGenerated(gf generated.EverythingFile) EverythingFile { DownloadURL: gf.DownloadUrl, AppDownloadURL: gf.AppDownloadUrl, Description: gf.Description, - } - if gf.Id != nil { - f.ID = *gf.Id + Content: gf.Content, } if gf.Width != nil { w := int32(*gf.Width) @@ -163,6 +170,7 @@ func everythingFileFromGenerated(gf generated.EverythingFile) EverythingFile { f.Creator = &creator } f.DescriptionAttachments = richTextAttachmentsPtrFromGenerated(gf.DescriptionAttachments) + f.ContentAttachments = richTextAttachmentsPtrFromGenerated(gf.ContentAttachments) return f } diff --git a/go/pkg/basecamp/everything_test.go b/go/pkg/basecamp/everything_test.go index 965c25d6c..ecadb4159 100644 --- a/go/pkg/basecamp/everything_test.go +++ b/go/pkg/basecamp/everything_test.go @@ -109,7 +109,7 @@ func TestEverythingService_Files_PerVariantDecode(t *testing.T) { w.WriteHeader(200) _, _ = w.Write([]byte(`[ {"id":900,"type":"Upload","title":"logo.png","filename":"logo.png","content_type":"image/png","byte_size":1281,"width":1024.0,"height":768.0,"url":"https://x/uploads/900.json","download_url":"https://x/d/900","app_download_url":"https://storage/900","bucket":{"id":9,"name":"P","type":"Project"}}, - {"id":901,"type":"Document","title":"Spec","url":"https://x/documents/901.json","content_type":"text/html","bucket":{"id":9,"name":"P","type":"Project"}}, + {"id":901,"type":"Document","title":"Spec","url":"https://x/documents/901.json","content":"
Body
","content_attachments":[{"sgid":"sgid-doc","content_type":"image/png"}],"bucket":{"id":9,"name":"P","type":"Project"}}, {"id":902,"type":"Attachment","attachable_sgid":"sgid-902","filename":"chart.avif","content_type":"image/avif","byte_size":4096,"width":null,"height":null,"download_url":"https://storage/blobs/902","parent":{"id":800,"title":"A message","type":"Message"}} ]`)) }) @@ -131,10 +131,26 @@ func TestEverythingService_Files_PerVariantDecode(t *testing.T) { if up.AttachableSGID != "" { t.Errorf("upload variant should not carry attachable_sgid") } + // Presence-faithful pointers: the Upload carries byte_size, the Document + // omits it (must decode nil, not a fabricated 0), per SPEC §10. + if up.ByteSize == nil || *up.ByteSize != 1281 { + t.Errorf("expected upload byte_size 1281, got %v", up.ByteSize) + } doc := result.Files[1] if doc.Type != "Document" || doc.Title != "Spec" { t.Errorf("document variant not decoded: %+v", doc) } + // The Document body (content + content_attachments) must decode, not be + // dropped by the superset (finding #7). + if doc.Content != "
Body
" { + t.Errorf("expected document content to decode, got %q", doc.Content) + } + if doc.ContentAttachments == nil || len(*doc.ContentAttachments) != 1 { + t.Errorf("expected 1 document content attachment, got %v", doc.ContentAttachments) + } + if doc.ByteSize != nil { + t.Errorf("expected nil byte_size on the document variant, got %v", *doc.ByteSize) + } att := result.Files[2] if att.Type != "Attachment" || att.AttachableSGID != "sgid-902" || att.Parent == nil { t.Errorf("attachment variant not decoded: %+v", att) diff --git a/go/pkg/generated/client.gen.go b/go/pkg/generated/client.gen.go index 818ceaf21..df55888f1 100644 --- a/go/pkg/generated/client.gen.go +++ b/go/pkg/generated/client.gen.go @@ -864,15 +864,19 @@ type EverythingFile struct { // attachment (uploads/documents omit it). AttachableSgid string `json:"attachable_sgid,omitempty"` BookmarkUrl string `json:"bookmark_url,omitempty"` - BoostsCount int32 `json:"boosts_count,omitempty"` + BoostsCount *int32 `json:"boosts_count,omitempty"` BoostsUrl string `json:"boosts_url,omitempty"` Bucket RecordingBucket `json:"bucket,omitempty"` - ByteSize int64 `json:"byte_size,omitempty"` - CommentsCount int32 `json:"comments_count,omitempty"` + ByteSize *int64 `json:"byte_size,omitempty"` + CommentsCount *int32 `json:"comments_count,omitempty"` CommentsUrl string `json:"comments_url,omitempty"` - ContentType string `json:"content_type,omitempty"` - CreatedAt *time.Time `json:"created_at,omitempty"` - Creator Person `json:"creator,omitempty"` + + // Content Rich-text body of the Document variant (uploads/attachments omit it). + Content string `json:"content,omitempty"` + ContentAttachments []RichTextAttachment `json:"content_attachments,omitempty"` + ContentType string `json:"content_type,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + Creator Person `json:"creator,omitempty"` // Description Rich-text description (upload/document variants). Description string `json:"description,omitempty"` @@ -887,7 +891,7 @@ type EverythingFile struct { Id *int64 `json:"id,omitempty"` InheritsStatus *bool `json:"inherits_status,omitempty"` Parent RecordingParent `json:"parent,omitempty"` - Position int32 `json:"position,omitempty"` + Position *int32 `json:"position,omitempty"` Status string `json:"status,omitempty"` SubscriptionUrl string `json:"subscription_url,omitempty"` Title string `json:"title,omitempty"` diff --git a/kotlin/generator/src/main/kotlin/com/basecamp/sdk/generator/ModelEmitter.kt b/kotlin/generator/src/main/kotlin/com/basecamp/sdk/generator/ModelEmitter.kt index b755d3b86..1f5843034 100644 --- a/kotlin/generator/src/main/kotlin/com/basecamp/sdk/generator/ModelEmitter.kt +++ b/kotlin/generator/src/main/kotlin/com/basecamp/sdk/generator/ModelEmitter.kt @@ -182,12 +182,15 @@ class ModelEmitter(private val api: OpenApiParser) { } return when (schema["type"]?.jsonPrimitive?.content) { + // Optional scalars are nullable so an absent field decodes to null + // rather than a fabricated 0/false sentinel, per SPEC.md §10 (Optional + // Fields). Required scalars stay non-nullable. "integer" -> when (schema["format"]?.jsonPrimitive?.content) { - "int64" -> "Long" - else -> "Int" + "int64" -> if (isRequired) "Long" else "Long?" + else -> if (isRequired) "Int" else "Int?" } - "boolean" -> "Boolean" - "number" -> "Double" + "boolean" -> if (isRequired) "Boolean" else "Boolean?" + "number" -> if (isRequired) "Double" else "Double?" "string" -> if (isRequired) "String" else "String?" "array" -> { val itemType = resolveArrayItemType(schema["items"]?.jsonObject) diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Answer.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Answer.kt index c41e87391..5b6d2c30e 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Answer.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Answer.kt @@ -29,9 +29,9 @@ data class Answer( val creator: Person, @SerialName("bookmark_url") val bookmarkUrl: String? = null, @SerialName("subscription_url") val subscriptionUrl: String? = null, - @SerialName("comments_count") val commentsCount: Int = 0, + @SerialName("comments_count") val commentsCount: Int? = null, @SerialName("comments_url") val commentsUrl: String? = null, @SerialName("group_on") val groupOn: String? = null, - @SerialName("boosts_count") val boostsCount: Int = 0, + @SerialName("boosts_count") val boostsCount: Int? = null, @SerialName("boosts_url") val boostsUrl: String? = null ) diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Campfire.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Campfire.kt index 298733628..0f4ab0c5f 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Campfire.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Campfire.kt @@ -26,7 +26,7 @@ data class Campfire( val creator: Person, @SerialName("bookmark_url") val bookmarkUrl: String? = null, @SerialName("subscription_url") val subscriptionUrl: String? = null, - val position: Int = 0, + val position: Int? = null, val topic: String? = null, @SerialName("lines_url") val linesUrl: String? = null, @SerialName("files_url") val filesUrl: String? = null diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/CampfireLine.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/CampfireLine.kt index 64b8fc621..119572a22 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/CampfireLine.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/CampfireLine.kt @@ -28,6 +28,6 @@ data class CampfireLine( @SerialName("bookmark_url") val bookmarkUrl: String? = null, val content: String? = null, val attachments: List = emptyList(), - @SerialName("boosts_count") val boostsCount: Int = 0, + @SerialName("boosts_count") val boostsCount: Int? = null, @SerialName("boosts_url") val boostsUrl: String? = null ) diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/CampfireLineAttachment.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/CampfireLineAttachment.kt index 1d890444f..5202e81dd 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/CampfireLineAttachment.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/CampfireLineAttachment.kt @@ -16,6 +16,6 @@ data class CampfireLineAttachment( val url: String? = null, val filename: String? = null, @SerialName("content_type") val contentType: String? = null, - @SerialName("byte_size") val byteSize: Long = 0L, + @SerialName("byte_size") val byteSize: Long? = null, @SerialName("download_url") val downloadUrl: String? = null ) diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Card.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Card.kt index 1bcd82524..037a07232 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Card.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Card.kt @@ -28,19 +28,19 @@ data class Card( val creator: Person, @SerialName("bookmark_url") val bookmarkUrl: String? = null, @SerialName("subscription_url") val subscriptionUrl: String? = null, - val position: Int = 0, + val position: Int? = null, val content: String? = null, val description: String? = null, @SerialName("due_on") val dueOn: String? = null, - val completed: Boolean = false, + val completed: Boolean? = null, @SerialName("completed_at") val completedAt: String? = null, - @SerialName("comments_count") val commentsCount: Int = 0, + @SerialName("comments_count") val commentsCount: Int? = null, @SerialName("comments_url") val commentsUrl: String? = null, @SerialName("completion_url") val completionUrl: String? = null, val completer: Person? = null, val assignees: List = emptyList(), @SerialName("completion_subscribers") val completionSubscribers: List = emptyList(), val steps: List = emptyList(), - @SerialName("boosts_count") val boostsCount: Int = 0, + @SerialName("boosts_count") val boostsCount: Int? = null, @SerialName("boosts_url") val boostsUrl: String? = null ) diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/CardColumn.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/CardColumn.kt index b6e6c21fe..ddfc038fc 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/CardColumn.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/CardColumn.kt @@ -26,11 +26,11 @@ data class CardColumn( val bucket: TodoBucket, val creator: Person, @SerialName("bookmark_url") val bookmarkUrl: String? = null, - val position: Int = 0, + val position: Int? = null, val color: String? = null, val description: String? = null, - @SerialName("cards_count") val cardsCount: Int = 0, - @SerialName("comments_count") val commentsCount: Int = 0, + @SerialName("cards_count") val cardsCount: Int? = null, + @SerialName("comments_count") val commentsCount: Int? = null, @SerialName("cards_url") val cardsUrl: String? = null, val subscribers: List = emptyList(), @SerialName("on_hold") val onHold: CardColumnOnHold? = null diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/CardStep.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/CardStep.kt index 442ff237e..bf96c2d16 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/CardStep.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/CardStep.kt @@ -26,9 +26,9 @@ data class CardStep( val bucket: TodoBucket, val creator: Person, @SerialName("bookmark_url") val bookmarkUrl: String? = null, - val position: Int = 0, + val position: Int? = null, @SerialName("due_on") val dueOn: String? = null, - val completed: Boolean = false, + val completed: Boolean? = null, @SerialName("completed_at") val completedAt: String? = null, val completer: Person? = null, val assignees: List = emptyList(), diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/ClientApproval.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/ClientApproval.kt index 52d084528..c3b50ec02 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/ClientApproval.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/ClientApproval.kt @@ -31,7 +31,7 @@ data class ClientApproval( val content: String? = null, val subject: String? = null, @SerialName("due_on") val dueOn: String? = null, - @SerialName("replies_count") val repliesCount: Int = 0, + @SerialName("replies_count") val repliesCount: Int? = null, @SerialName("replies_url") val repliesUrl: String? = null, @SerialName("approval_status") val approvalStatus: String? = null, val approver: Person? = null, diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/ClientApprovalResponse.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/ClientApprovalResponse.kt index 21ba9d576..9eea389e0 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/ClientApprovalResponse.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/ClientApprovalResponse.kt @@ -12,13 +12,13 @@ import kotlinx.serialization.json.JsonObject */ @Serializable data class ClientApprovalResponse( - val id: Long = 0L, + val id: Long? = null, val status: String? = null, - @SerialName("visible_to_clients") val visibleToClients: Boolean = false, + @SerialName("visible_to_clients") val visibleToClients: Boolean? = null, @SerialName("created_at") val createdAt: String? = null, @SerialName("updated_at") val updatedAt: String? = null, val title: String? = null, - @SerialName("inherits_status") val inheritsStatus: Boolean = false, + @SerialName("inherits_status") val inheritsStatus: Boolean? = null, val type: String? = null, @SerialName("app_url") val appUrl: String? = null, @SerialName("bookmark_url") val bookmarkUrl: String? = null, @@ -26,5 +26,5 @@ data class ClientApprovalResponse( val bucket: RecordingBucket? = null, val creator: Person? = null, val content: String? = null, - val approved: Boolean = false + val approved: Boolean? = null ) diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/ClientCorrespondence.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/ClientCorrespondence.kt index 97f5e90b7..cf1b58ddc 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/ClientCorrespondence.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/ClientCorrespondence.kt @@ -30,6 +30,6 @@ data class ClientCorrespondence( @SerialName("bookmark_url") val bookmarkUrl: String? = null, @SerialName("subscription_url") val subscriptionUrl: String? = null, val content: String? = null, - @SerialName("replies_count") val repliesCount: Int = 0, + @SerialName("replies_count") val repliesCount: Int? = null, @SerialName("replies_url") val repliesUrl: String? = null ) diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Comment.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Comment.kt index 1c29afa0e..a02c33928 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Comment.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Comment.kt @@ -28,6 +28,6 @@ data class Comment( val content: String, @SerialName("content_attachments") val contentAttachments: List, @SerialName("bookmark_url") val bookmarkUrl: String? = null, - @SerialName("boosts_count") val boostsCount: Int = 0, + @SerialName("boosts_count") val boostsCount: Int? = null, @SerialName("boosts_url") val boostsUrl: String? = null ) diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/DockItem.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/DockItem.kt index 164cf15cb..a900ac5c6 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/DockItem.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/DockItem.kt @@ -18,5 +18,5 @@ data class DockItem( val enabled: Boolean, val url: String, @SerialName("app_url") val appUrl: String, - val position: Int = 0 + val position: Int? = null ) diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Document.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Document.kt index 8dd631ad3..dc4553c71 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Document.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Document.kt @@ -28,10 +28,10 @@ data class Document( @SerialName("content_attachments") val contentAttachments: List, @SerialName("bookmark_url") val bookmarkUrl: String? = null, @SerialName("subscription_url") val subscriptionUrl: String? = null, - @SerialName("comments_count") val commentsCount: Int = 0, + @SerialName("comments_count") val commentsCount: Int? = null, @SerialName("comments_url") val commentsUrl: String? = null, - val position: Int = 0, + val position: Int? = null, val content: String? = null, - @SerialName("boosts_count") val boostsCount: Int = 0, + @SerialName("boosts_count") val boostsCount: Int? = null, @SerialName("boosts_url") val boostsUrl: String? = null ) diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Event.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Event.kt index 4e24b21e3..bc6714969 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Event.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Event.kt @@ -18,6 +18,6 @@ data class Event( @SerialName("created_at") val createdAt: String, val creator: Person, val details: EventDetails? = null, - @SerialName("boosts_count") val boostsCount: Int = 0, + @SerialName("boosts_count") val boostsCount: Int? = null, @SerialName("boosts_url") val boostsUrl: String? = null ) diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/EverythingFile.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/EverythingFile.kt index c0459879e..3afb2205a 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/EverythingFile.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/EverythingFile.kt @@ -13,29 +13,29 @@ import com.basecamp.sdk.serialization.FlexibleIntSerializer */ @Serializable data class EverythingFile( - val id: Long = 0L, + val id: Long? = null, val status: String? = null, - @SerialName("visible_to_clients") val visibleToClients: Boolean = false, + @SerialName("visible_to_clients") val visibleToClients: Boolean? = null, @SerialName("created_at") val createdAt: String? = null, @SerialName("updated_at") val updatedAt: String? = null, val title: String? = null, - @SerialName("inherits_status") val inheritsStatus: Boolean = false, + @SerialName("inherits_status") val inheritsStatus: Boolean? = null, val type: String? = null, val url: String? = null, @SerialName("app_url") val appUrl: String? = null, @SerialName("bookmark_url") val bookmarkUrl: String? = null, @SerialName("subscription_url") val subscriptionUrl: String? = null, - @SerialName("comments_count") val commentsCount: Int = 0, + @SerialName("comments_count") val commentsCount: Int? = null, @SerialName("comments_url") val commentsUrl: String? = null, - @SerialName("boosts_count") val boostsCount: Int = 0, + @SerialName("boosts_count") val boostsCount: Int? = null, @SerialName("boosts_url") val boostsUrl: String? = null, - val position: Int = 0, + val position: Int? = null, val parent: RecordingParent? = null, val bucket: RecordingBucket? = null, val creator: Person? = null, @SerialName("attachable_sgid") val attachableSgid: String? = null, @SerialName("content_type") val contentType: String? = null, - @SerialName("byte_size") val byteSize: Long = 0L, + @SerialName("byte_size") val byteSize: Long? = null, val filename: String? = null, @SerialName("download_url") val downloadUrl: String? = null, @SerialName("app_download_url") val appDownloadUrl: String? = null, @@ -44,5 +44,7 @@ data class EverythingFile( @Serializable(with = FlexibleIntSerializer::class) val height: Int? = null, val description: String? = null, - @SerialName("description_attachments") val descriptionAttachments: List? = null + @SerialName("description_attachments") val descriptionAttachments: List? = null, + val content: String? = null, + @SerialName("content_attachments") val contentAttachments: List? = null ) diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Forward.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Forward.kt index beacf1a17..4c048ba11 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Forward.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Forward.kt @@ -31,6 +31,6 @@ data class Forward( @SerialName("subscription_url") val subscriptionUrl: String? = null, val content: String? = null, val from: String? = null, - @SerialName("replies_count") val repliesCount: Int = 0, + @SerialName("replies_count") val repliesCount: Int? = null, @SerialName("replies_url") val repliesUrl: String? = null ) diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/ForwardReply.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/ForwardReply.kt index 1ae6065b8..2574f98f5 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/ForwardReply.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/ForwardReply.kt @@ -28,6 +28,6 @@ data class ForwardReply( val content: String, @SerialName("content_attachments") val contentAttachments: List, @SerialName("bookmark_url") val bookmarkUrl: String? = null, - @SerialName("boosts_count") val boostsCount: Int = 0, + @SerialName("boosts_count") val boostsCount: Int? = null, @SerialName("boosts_url") val boostsUrl: String? = null ) diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Inbox.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Inbox.kt index 4f247f29a..9b831dead 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Inbox.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Inbox.kt @@ -25,7 +25,7 @@ data class Inbox( val bucket: TodoBucket, val creator: Person, @SerialName("bookmark_url") val bookmarkUrl: String? = null, - val position: Int = 0, - @SerialName("forwards_count") val forwardsCount: Int = 0, + val position: Int? = null, + @SerialName("forwards_count") val forwardsCount: Int? = null, @SerialName("forwards_url") val forwardsUrl: String? = null ) diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Message.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Message.kt index 038c78929..6b8b78542 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Message.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Message.kt @@ -30,9 +30,9 @@ data class Message( @SerialName("content_attachments") val contentAttachments: List, @SerialName("bookmark_url") val bookmarkUrl: String? = null, @SerialName("subscription_url") val subscriptionUrl: String? = null, - @SerialName("comments_count") val commentsCount: Int = 0, + @SerialName("comments_count") val commentsCount: Int? = null, @SerialName("comments_url") val commentsUrl: String? = null, val category: MessageType? = null, - @SerialName("boosts_count") val boostsCount: Int = 0, + @SerialName("boosts_count") val boostsCount: Int? = null, @SerialName("boosts_url") val boostsUrl: String? = null ) diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/MessageBoard.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/MessageBoard.kt index 435f0d352..bbe4caf36 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/MessageBoard.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/MessageBoard.kt @@ -25,8 +25,8 @@ data class MessageBoard( val bucket: TodoBucket, val creator: Person, @SerialName("bookmark_url") val bookmarkUrl: String? = null, - val position: Int = 0, - @SerialName("messages_count") val messagesCount: Int = 0, + val position: Int? = null, + @SerialName("messages_count") val messagesCount: Int? = null, @SerialName("messages_url") val messagesUrl: String? = null, @SerialName("app_messages_url") val appMessagesUrl: String? = null ) diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Notification.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Notification.kt index 783bdb227..30dcd2539 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Notification.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Notification.kt @@ -16,7 +16,7 @@ data class Notification( @SerialName("created_at") val createdAt: String, @SerialName("updated_at") val updatedAt: String, val section: String? = null, - @SerialName("unread_count") val unreadCount: Int = 0, + @SerialName("unread_count") val unreadCount: Int? = null, @SerialName("unread_at") val unreadAt: String? = null, @SerialName("read_at") val readAt: String? = null, @SerialName("readable_sgid") val readableSgid: String? = null, @@ -33,9 +33,9 @@ data class Notification( @SerialName("bubble_up_url") val bubbleUpUrl: String? = null, @SerialName("bubble_up_at") val bubbleUpAt: String? = null, @SerialName("subscription_url") val subscriptionUrl: String? = null, - val subscribed: Boolean = false, + val subscribed: Boolean? = null, @SerialName("previewable_attachments") val previewableAttachments: List = emptyList(), val participants: List = emptyList(), - val named: Boolean = false, + val named: Boolean? = null, @SerialName("image_url") val imageUrl: String? = null ) diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Person.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Person.kt index 72df7ef47..d4f3a83ce 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Person.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Person.kt @@ -26,16 +26,16 @@ data class Person( val location: String? = null, @SerialName("created_at") val createdAt: String? = null, @SerialName("updated_at") val updatedAt: String? = null, - val admin: Boolean = false, - val owner: Boolean = false, - val client: Boolean = false, - val employee: Boolean = false, + val admin: Boolean? = null, + val owner: Boolean? = null, + val client: Boolean? = null, + val employee: Boolean? = null, @SerialName("time_zone") val timeZone: String? = null, @SerialName("avatar_url") val avatarUrl: String? = null, val company: PersonCompany? = null, - @SerialName("can_manage_projects") val canManageProjects: Boolean = false, - @SerialName("can_manage_people") val canManagePeople: Boolean = false, - @SerialName("can_ping") val canPing: Boolean = false, - @SerialName("can_access_timesheet") val canAccessTimesheet: Boolean = false, - @SerialName("can_access_hill_charts") val canAccessHillCharts: Boolean = false + @SerialName("can_manage_projects") val canManageProjects: Boolean? = null, + @SerialName("can_manage_people") val canManagePeople: Boolean? = null, + @SerialName("can_ping") val canPing: Boolean? = null, + @SerialName("can_access_timesheet") val canAccessTimesheet: Boolean? = null, + @SerialName("can_access_hill_charts") val canAccessHillCharts: Boolean? = null ) diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/PreviewableAttachment.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/PreviewableAttachment.kt index d1ce9a793..305cb3ced 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/PreviewableAttachment.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/PreviewableAttachment.kt @@ -12,12 +12,12 @@ import kotlinx.serialization.json.JsonObject */ @Serializable data class PreviewableAttachment( - val id: Long = 0L, + val id: Long? = null, val url: String? = null, @SerialName("app_url") val appUrl: String? = null, @SerialName("content_type") val contentType: String? = null, val filename: String? = null, - val filesize: Long = 0L, - val width: Int = 0, - val height: Int = 0 + val filesize: Long? = null, + val width: Int? = null, + val height: Int? = null ) diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Project.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Project.kt index e7cc5383a..679a010eb 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Project.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Project.kt @@ -24,10 +24,10 @@ data class Project( val purpose: String? = null, @SerialName("start_date") val startDate: String? = null, @SerialName("end_date") val endDate: String? = null, - @SerialName("clients_enabled") val clientsEnabled: Boolean = false, + @SerialName("clients_enabled") val clientsEnabled: Boolean? = null, @SerialName("bookmark_url") val bookmarkUrl: String? = null, val dock: List = emptyList(), - val bookmarked: Boolean = false, + val bookmarked: Boolean? = null, @SerialName("client_company") val clientCompany: ClientCompany? = null, @Deprecated("This shape is deprecated since 2024-01: Use Client Visibility feature instead") val clientside: ClientSide? = null diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Question.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Question.kt index 159a1c508..d5a5ca655 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Question.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Question.kt @@ -27,8 +27,8 @@ data class Question( val creator: Person, @SerialName("bookmark_url") val bookmarkUrl: String? = null, @SerialName("subscription_url") val subscriptionUrl: String? = null, - val paused: Boolean = false, + val paused: Boolean? = null, val schedule: QuestionSchedule? = null, - @SerialName("answers_count") val answersCount: Int = 0, + @SerialName("answers_count") val answersCount: Int? = null, @SerialName("answers_url") val answersUrl: String? = null ) diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/QuestionSchedule.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/QuestionSchedule.kt index 8b76611a3..e1f4c605e 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/QuestionSchedule.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/QuestionSchedule.kt @@ -14,11 +14,11 @@ import kotlinx.serialization.json.JsonObject data class QuestionSchedule( val frequency: String? = null, val days: List = emptyList(), - val hour: Int = 0, - val minute: Int = 0, - @SerialName("week_instance") val weekInstance: Int = 0, - @SerialName("week_interval") val weekInterval: Int = 0, - @SerialName("month_interval") val monthInterval: Int = 0, + val hour: Int? = null, + val minute: Int? = null, + @SerialName("week_instance") val weekInstance: Int? = null, + @SerialName("week_interval") val weekInterval: Int? = null, + @SerialName("month_interval") val monthInterval: Int? = null, @SerialName("start_date") val startDate: String? = null, @SerialName("end_date") val endDate: String? = null ) diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Questionnaire.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Questionnaire.kt index 6e32b3ef0..6deeed311 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Questionnaire.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Questionnaire.kt @@ -27,5 +27,5 @@ data class Questionnaire( val creator: Person, @SerialName("bookmark_url") val bookmarkUrl: String? = null, @SerialName("questions_url") val questionsUrl: String? = null, - @SerialName("questions_count") val questionsCount: Int = 0 + @SerialName("questions_count") val questionsCount: Int? = null ) diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Recording.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Recording.kt index dff55d691..57906c18a 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Recording.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Recording.kt @@ -28,10 +28,10 @@ data class Recording( val content: String? = null, @SerialName("content_attachments") val contentAttachments: List? = null, @SerialName("description_attachments") val descriptionAttachments: List? = null, - @SerialName("comments_count") val commentsCount: Int = 0, + @SerialName("comments_count") val commentsCount: Int? = null, @SerialName("comments_url") val commentsUrl: String? = null, @SerialName("subscription_url") val subscriptionUrl: String? = null, - val position: Int = 0, + val position: Int? = null, val description: String? = null, val service: DoorService? = null, val parent: RecordingParent? = null diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Schedule.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Schedule.kt index 0f9477a1d..7950b09a8 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Schedule.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Schedule.kt @@ -25,8 +25,8 @@ data class Schedule( val bucket: TodoBucket, val creator: Person, @SerialName("bookmark_url") val bookmarkUrl: String? = null, - val position: Int = 0, - @SerialName("include_due_assignments") val includeDueAssignments: Boolean = false, - @SerialName("entries_count") val entriesCount: Int = 0, + val position: Int? = null, + @SerialName("include_due_assignments") val includeDueAssignments: Boolean? = null, + @SerialName("entries_count") val entriesCount: Int? = null, @SerialName("entries_url") val entriesUrl: String? = null ) diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/ScheduleEntry.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/ScheduleEntry.kt index 7453dc3b8..76964f5c1 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/ScheduleEntry.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/ScheduleEntry.kt @@ -29,13 +29,13 @@ data class ScheduleEntry( @SerialName("description_attachments") val descriptionAttachments: List, @SerialName("bookmark_url") val bookmarkUrl: String? = null, @SerialName("subscription_url") val subscriptionUrl: String? = null, - @SerialName("comments_count") val commentsCount: Int = 0, + @SerialName("comments_count") val commentsCount: Int? = null, @SerialName("comments_url") val commentsUrl: String? = null, val description: String? = null, - @SerialName("all_day") val allDay: Boolean = false, + @SerialName("all_day") val allDay: Boolean? = null, @SerialName("starts_at") val startsAt: String? = null, @SerialName("ends_at") val endsAt: String? = null, val participants: List = emptyList(), - @SerialName("boosts_count") val boostsCount: Int = 0, + @SerialName("boosts_count") val boostsCount: Int? = null, @SerialName("boosts_url") val boostsUrl: String? = null ) diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/TimelineAttachment.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/TimelineAttachment.kt index 3c3222062..a2c842388 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/TimelineAttachment.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/TimelineAttachment.kt @@ -13,9 +13,9 @@ import com.basecamp.sdk.serialization.FlexibleIntSerializer */ @Serializable data class TimelineAttachment( - val id: Long = 0L, + val id: Long? = null, @SerialName("content_type") val contentType: String? = null, - @SerialName("byte_size") val byteSize: Long = 0L, + @SerialName("byte_size") val byteSize: Long? = null, val filename: String? = null, @SerialName("download_url") val downloadUrl: String? = null, @Serializable(with = FlexibleIntSerializer::class) @@ -30,13 +30,13 @@ data class TimelineAttachment( val url: String? = null, @SerialName("app_url") val appUrl: String? = null, @SerialName("app_download_url") val appDownloadUrl: String? = null, - @SerialName("visible_to_clients") val visibleToClients: Boolean = false, + @SerialName("visible_to_clients") val visibleToClients: Boolean? = null, @SerialName("attachable_sgid") val attachableSgid: String? = null, val sgid: String? = null, @SerialName("status_url") val statusUrl: String? = null, val caption: String? = null, val key: String? = null, - val previewable: Boolean = false, + val previewable: Boolean? = null, @SerialName("preview_url") val previewUrl: String? = null, @SerialName("thumbnail_url") val thumbnailUrl: String? = null ) diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/TimelineEvent.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/TimelineEvent.kt index fee3eb8dd..a423ae8d6 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/TimelineEvent.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/TimelineEvent.kt @@ -12,10 +12,10 @@ import kotlinx.serialization.json.JsonObject */ @Serializable data class TimelineEvent( - val id: Long = 0L, + val id: Long? = null, @SerialName("created_at") val createdAt: String? = null, val kind: String? = null, - @SerialName("parent_recording_id") val parentRecordingId: Long = 0L, + @SerialName("parent_recording_id") val parentRecordingId: Long? = null, val url: String? = null, @SerialName("app_url") val appUrl: String? = null, val creator: Person? = null, diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/TimelineEventData.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/TimelineEventData.kt index 69832d0c5..0a64b48c2 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/TimelineEventData.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/TimelineEventData.kt @@ -12,7 +12,7 @@ import kotlinx.serialization.json.JsonObject */ @Serializable data class TimelineEventData( - @SerialName("all_day") val allDay: Boolean = false, + @SerialName("all_day") val allDay: Boolean? = null, @SerialName("starts_at") val startsAt: String? = null, @SerialName("ends_at") val endsAt: String? = null ) diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Todo.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Todo.kt index 4bba09258..859a70406 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Todo.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Todo.kt @@ -29,17 +29,17 @@ data class Todo( @SerialName("description_attachments") val descriptionAttachments: List, @SerialName("bookmark_url") val bookmarkUrl: String? = null, @SerialName("subscription_url") val subscriptionUrl: String? = null, - @SerialName("comments_count") val commentsCount: Int = 0, + @SerialName("comments_count") val commentsCount: Int? = null, @SerialName("comments_url") val commentsUrl: String? = null, - val position: Int = 0, + val position: Int? = null, val description: String? = null, - val completed: Boolean = false, + val completed: Boolean? = null, @SerialName("starts_on") val startsOn: String? = null, @SerialName("due_on") val dueOn: String? = null, val assignees: List = emptyList(), @SerialName("completion_subscribers") val completionSubscribers: List = emptyList(), @SerialName("completion_url") val completionUrl: String? = null, - @SerialName("boosts_count") val boostsCount: Int = 0, + @SerialName("boosts_count") val boostsCount: Int? = null, @SerialName("boosts_url") val boostsUrl: String? = null, val steps: List = emptyList() ) diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Todolist.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Todolist.kt index 023990044..8530b3812 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Todolist.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Todolist.kt @@ -29,15 +29,15 @@ data class Todolist( val name: String, @SerialName("bookmark_url") val bookmarkUrl: String? = null, @SerialName("subscription_url") val subscriptionUrl: String? = null, - @SerialName("comments_count") val commentsCount: Int = 0, + @SerialName("comments_count") val commentsCount: Int? = null, @SerialName("comments_url") val commentsUrl: String? = null, - val position: Int = 0, + val position: Int? = null, val description: String? = null, - val completed: Boolean = false, + val completed: Boolean? = null, @SerialName("completed_ratio") val completedRatio: String? = null, @SerialName("todos_url") val todosUrl: String? = null, @SerialName("groups_url") val groupsUrl: String? = null, @SerialName("app_todos_url") val appTodosUrl: String? = null, - @SerialName("boosts_count") val boostsCount: Int = 0, + @SerialName("boosts_count") val boostsCount: Int? = null, @SerialName("boosts_url") val boostsUrl: String? = null ) diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/TodolistGroup.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/TodolistGroup.kt index 6ae776611..30226340e 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/TodolistGroup.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/TodolistGroup.kt @@ -28,10 +28,10 @@ data class TodolistGroup( val name: String, @SerialName("bookmark_url") val bookmarkUrl: String? = null, @SerialName("subscription_url") val subscriptionUrl: String? = null, - @SerialName("comments_count") val commentsCount: Int = 0, + @SerialName("comments_count") val commentsCount: Int? = null, @SerialName("comments_url") val commentsUrl: String? = null, - val position: Int = 0, - val completed: Boolean = false, + val position: Int? = null, + val completed: Boolean? = null, @SerialName("completed_ratio") val completedRatio: String? = null, @SerialName("todos_url") val todosUrl: String? = null, @SerialName("app_todos_url") val appTodosUrl: String? = null diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Todoset.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Todoset.kt index 0e9dddd5f..3e9cd3cff 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Todoset.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Todoset.kt @@ -26,14 +26,14 @@ data class Todoset( val creator: Person, val name: String, @SerialName("bookmark_url") val bookmarkUrl: String? = null, - val position: Int = 0, - @SerialName("todolists_count") val todolistsCount: Int = 0, + val position: Int? = null, + @SerialName("todolists_count") val todolistsCount: Int? = null, @SerialName("todolists_url") val todolistsUrl: String? = null, @SerialName("completed_ratio") val completedRatio: String? = null, - val completed: Boolean = false, + val completed: Boolean? = null, @SerialName("app_todolists_url") val appTodolistsUrl: String? = null, - @SerialName("todos_count") val todosCount: Int = 0, - @SerialName("completed_loose_todos_count") val completedLooseTodosCount: Int = 0, + @SerialName("todos_count") val todosCount: Int? = null, + @SerialName("completed_loose_todos_count") val completedLooseTodosCount: Int? = null, @SerialName("todos_url") val todosUrl: String? = null, @SerialName("app_todos_url") val appTodosUrl: String? = null ) diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Tool.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Tool.kt index e950161a5..2dcac9cc2 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Tool.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Tool.kt @@ -19,7 +19,7 @@ data class Tool( val name: String, val enabled: Boolean, val status: String? = null, - val position: Int = 0, + val position: Int? = null, val url: String? = null, @SerialName("app_url") val appUrl: String? = null, val bucket: RecordingBucket? = null diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Upload.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Upload.kt index 4690db46f..3d5d51c7a 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Upload.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Upload.kt @@ -29,18 +29,18 @@ data class Upload( @SerialName("description_attachments") val descriptionAttachments: List, @SerialName("bookmark_url") val bookmarkUrl: String? = null, @SerialName("subscription_url") val subscriptionUrl: String? = null, - @SerialName("comments_count") val commentsCount: Int = 0, + @SerialName("comments_count") val commentsCount: Int? = null, @SerialName("comments_url") val commentsUrl: String? = null, - val position: Int = 0, + val position: Int? = null, val description: String? = null, @SerialName("content_type") val contentType: String? = null, - @SerialName("byte_size") val byteSize: Long = 0L, + @SerialName("byte_size") val byteSize: Long? = null, @Serializable(with = FlexibleIntSerializer::class) val width: Int? = null, @Serializable(with = FlexibleIntSerializer::class) val height: Int? = null, @SerialName("download_url") val downloadUrl: String? = null, val filename: String? = null, - @SerialName("boosts_count") val boostsCount: Int = 0, + @SerialName("boosts_count") val boostsCount: Int? = null, @SerialName("boosts_url") val boostsUrl: String? = null ) diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Vault.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Vault.kt index a262e9019..f1e474180 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Vault.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Vault.kt @@ -25,12 +25,12 @@ data class Vault( val bucket: TodoBucket, val creator: Person, @SerialName("bookmark_url") val bookmarkUrl: String? = null, - val position: Int = 0, + val position: Int? = null, val parent: RecordingParent? = null, - @SerialName("documents_count") val documentsCount: Int = 0, + @SerialName("documents_count") val documentsCount: Int? = null, @SerialName("documents_url") val documentsUrl: String? = null, - @SerialName("uploads_count") val uploadsCount: Int = 0, + @SerialName("uploads_count") val uploadsCount: Int? = null, @SerialName("uploads_url") val uploadsUrl: String? = null, - @SerialName("vaults_count") val vaultsCount: Int = 0, + @SerialName("vaults_count") val vaultsCount: Int? = null, @SerialName("vaults_url") val vaultsUrl: String? = null ) diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Webhook.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Webhook.kt index 03f8f0208..c642e3e5a 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Webhook.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/Webhook.kt @@ -18,7 +18,7 @@ data class Webhook( @SerialName("payload_url") val payloadUrl: String, val url: String, @SerialName("app_url") val appUrl: String, - val active: Boolean = false, + val active: Boolean? = null, val types: List = emptyList(), @SerialName("recent_deliveries") val recentDeliveries: List = emptyList() ) diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/WebhookCopy.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/WebhookCopy.kt index de1456704..3840c90bd 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/WebhookCopy.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/WebhookCopy.kt @@ -12,7 +12,7 @@ import kotlinx.serialization.json.JsonObject */ @Serializable data class WebhookCopy( - val id: Long = 0L, + val id: Long? = null, val url: String? = null, @SerialName("app_url") val appUrl: String? = null, val bucket: WebhookCopyBucket? = null diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/WebhookCopyBucket.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/WebhookCopyBucket.kt index f8dcaf672..411d71fdd 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/WebhookCopyBucket.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/WebhookCopyBucket.kt @@ -12,5 +12,5 @@ import kotlinx.serialization.json.JsonObject */ @Serializable data class WebhookCopyBucket( - val id: Long = 0L + val id: Long? = null ) diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/WebhookDelivery.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/WebhookDelivery.kt index cdbe5d32c..8e83d7cf7 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/WebhookDelivery.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/WebhookDelivery.kt @@ -12,7 +12,7 @@ import kotlinx.serialization.json.JsonObject */ @Serializable data class WebhookDelivery( - val id: Long = 0L, + val id: Long? = null, @SerialName("created_at") val createdAt: String? = null, val request: WebhookDeliveryRequest? = null, val response: WebhookDeliveryResponse? = null diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/WebhookDeliveryResponse.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/WebhookDeliveryResponse.kt index a89c83556..c350aa7a0 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/WebhookDeliveryResponse.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/WebhookDeliveryResponse.kt @@ -13,6 +13,6 @@ import kotlinx.serialization.json.JsonObject @Serializable data class WebhookDeliveryResponse( val headers: JsonObject? = null, - val code: Int = 0, + val code: Int? = null, val message: String? = null ) diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/WebhookEvent.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/WebhookEvent.kt index 6bc1706c8..8e30534a8 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/WebhookEvent.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/models/WebhookEvent.kt @@ -12,7 +12,7 @@ import kotlinx.serialization.json.JsonObject */ @Serializable data class WebhookEvent( - val id: Long = 0L, + val id: Long? = null, val kind: String? = null, val details: JsonElement? = null, @SerialName("created_at") val createdAt: String? = null, diff --git a/kotlin/sdk/src/commonTest/kotlin/com/basecamp/sdk/TimelineDecodeTest.kt b/kotlin/sdk/src/commonTest/kotlin/com/basecamp/sdk/TimelineDecodeTest.kt index 0ccdbfb51..d9b2e5174 100644 --- a/kotlin/sdk/src/commonTest/kotlin/com/basecamp/sdk/TimelineDecodeTest.kt +++ b/kotlin/sdk/src/commonTest/kotlin/com/basecamp/sdk/TimelineDecodeTest.kt @@ -116,7 +116,7 @@ class TimelineDecodeTest { assertEquals("sgid-attachable-500", blob[0].attachableSgid) assertEquals("See attached", blob[0].caption) assertEquals("blobkey500", blob[0].key) - assertTrue(blob[0].previewable) + assertEquals(true, blob[0].previewable) assertNull(blob[0].width) } } diff --git a/openapi.json b/openapi.json index abd0dc878..ddf879417 100644 --- a/openapi.json +++ b/openapi.json @@ -24774,21 +24774,24 @@ }, "comments_count": { "type": "integer", - "format": "int32" + "format": "int32", + "x-go-type-skip-optional-pointer": false }, "comments_url": { "type": "string" }, "boosts_count": { "type": "integer", - "format": "int32" + "format": "int32", + "x-go-type-skip-optional-pointer": false }, "boosts_url": { "type": "string" }, "position": { "type": "integer", - "format": "int32" + "format": "int32", + "x-go-type-skip-optional-pointer": false }, "parent": { "$ref": "#/components/schemas/RecordingParent" @@ -24808,7 +24811,8 @@ }, "byte_size": { "type": "integer", - "format": "int64" + "format": "int64", + "x-go-type-skip-optional-pointer": false }, "filename": { "type": "string" @@ -24851,6 +24855,16 @@ "items": { "$ref": "#/components/schemas/RichTextAttachment" } + }, + "content": { + "type": "string", + "description": "Rich-text body of the Document variant (uploads/attachments omit it)." + }, + "content_attachments": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RichTextAttachment" + } } } }, diff --git a/python/src/basecamp/generated/types.py b/python/src/basecamp/generated/types.py index a8dac6fea..452bce619 100644 --- a/python/src/basecamp/generated/types.py +++ b/python/src/basecamp/generated/types.py @@ -624,6 +624,8 @@ class EverythingFile(TypedDict): byte_size: NotRequired[int] comments_count: NotRequired[int] comments_url: NotRequired[str] + content: NotRequired[str] + content_attachments: NotRequired[list[RichTextAttachment]] content_type: NotRequired[str] created_at: NotRequired[str] creator: NotRequired[Person] diff --git a/ruby/lib/basecamp/generated/types.rb b/ruby/lib/basecamp/generated/types.rb index f63de3956..897f28733 100644 --- a/ruby/lib/basecamp/generated/types.rb +++ b/ruby/lib/basecamp/generated/types.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Auto-generated from OpenAPI spec. Do not edit manually. -# Generated: 2026-07-25T17:48:06Z +# Generated: 2026-07-25T21:40:03Z require "json" require "time" @@ -1363,7 +1363,7 @@ def to_json(*args) # EverythingFile class EverythingFile include TypeHelpers - attr_accessor :app_download_url, :app_url, :attachable_sgid, :bookmark_url, :boosts_count, :boosts_url, :bucket, :byte_size, :comments_count, :comments_url, :content_type, :created_at, :creator, :description, :description_attachments, :download_url, :filename, :height, :id, :inherits_status, :parent, :position, :status, :subscription_url, :title, :type, :updated_at, :url, :visible_to_clients, :width + attr_accessor :app_download_url, :app_url, :attachable_sgid, :bookmark_url, :boosts_count, :boosts_url, :bucket, :byte_size, :comments_count, :comments_url, :content, :content_attachments, :content_type, :created_at, :creator, :description, :description_attachments, :download_url, :filename, :height, :id, :inherits_status, :parent, :position, :status, :subscription_url, :title, :type, :updated_at, :url, :visible_to_clients, :width def initialize(data = {}) @app_download_url = data["app_download_url"] @@ -1376,6 +1376,8 @@ def initialize(data = {}) @byte_size = parse_integer(data["byte_size"]) @comments_count = parse_integer(data["comments_count"]) @comments_url = data["comments_url"] + @content = data["content"] + @content_attachments = parse_array(data["content_attachments"], "RichTextAttachment") @content_type = data["content_type"] @created_at = parse_datetime(data["created_at"]) @creator = parse_type(data["creator"], "Person") @@ -1410,6 +1412,8 @@ def to_h "byte_size" => @byte_size, "comments_count" => @comments_count, "comments_url" => @comments_url, + "content" => @content, + "content_attachments" => @content_attachments, "content_type" => @content_type, "created_at" => @created_at, "creator" => @creator, diff --git a/scripts/enhance-openapi-go-types.sh b/scripts/enhance-openapi-go-types.sh index 24e94395d..4e6fe916a 100755 --- a/scripts/enhance-openapi-go-types.sh +++ b/scripts/enhance-openapi-go-types.sh @@ -248,14 +248,20 @@ walk( | # Fifth-f pass: EverythingFile presence-faithful optional scalars (same rationale # as TimelineAttachment). The /files.json superset populates only one variant per -# instance, so its optional timestamps and booleans must round-trip presence: -# make created_at/updated_at *time.Time and visible_to_clients/inherits_status -# *bool so nil (absent variant) omits and an explicit false is preserved. +# instance, so its optional timestamps, booleans, and numeric scalars must +# round-trip presence: make created_at/updated_at *time.Time, +# visible_to_clients/inherits_status *bool, and the counts/position/byte_size +# pointers so nil (absent variant) omits and an explicit zero/false is preserved +# rather than fabricated. id is already an optional pointer via the oapi default. .components.schemas.EverythingFile.properties |= ( (.created_at // empty) += { "x-go-type-skip-optional-pointer": false } | (.updated_at // empty) += { "x-go-type-skip-optional-pointer": false } | (.visible_to_clients // empty) += { "x-go-type-skip-optional-pointer": false } | - (.inherits_status // empty) += { "x-go-type-skip-optional-pointer": false } + (.inherits_status // empty) += { "x-go-type-skip-optional-pointer": false } | + (.comments_count // empty) += { "x-go-type-skip-optional-pointer": false } | + (.boosts_count // empty) += { "x-go-type-skip-optional-pointer": false } | + (.position // empty) += { "x-go-type-skip-optional-pointer": false } | + (.byte_size // empty) += { "x-go-type-skip-optional-pointer": false } ) | # Sixth pass: Person.id → types.FlexibleInt64 diff --git a/spec/basecamp.smithy b/spec/basecamp.smithy index 0af4a671e..3fabebcb8 100644 --- a/spec/basecamp.smithy +++ b/spec/basecamp.smithy @@ -8971,6 +8971,10 @@ structure EverythingFile { /// Rich-text description (upload/document variants). description: String description_attachments: RichTextAttachmentList + + /// Rich-text body of the Document variant (uploads/attachments omit it). + content: DocumentContent + content_attachments: RichTextAttachmentList } // ===== My Assignment Shapes ===== diff --git a/swift/Sources/Basecamp/Generated/Models/EverythingFile.swift b/swift/Sources/Basecamp/Generated/Models/EverythingFile.swift index aad2f9025..ba824a7ef 100644 --- a/swift/Sources/Basecamp/Generated/Models/EverythingFile.swift +++ b/swift/Sources/Basecamp/Generated/Models/EverythingFile.swift @@ -12,6 +12,8 @@ public struct EverythingFile: Codable, Sendable { public var byteSize: Int? public var commentsCount: Int32? public var commentsUrl: String? + public var content: String? + public var contentAttachments: [RichTextAttachment]? public var contentType: String? public var createdAt: String? public var creator: Person? diff --git a/typescript/src/generated/openapi-stripped.json b/typescript/src/generated/openapi-stripped.json index 27886656e..7c18d8def 100644 --- a/typescript/src/generated/openapi-stripped.json +++ b/typescript/src/generated/openapi-stripped.json @@ -22295,21 +22295,24 @@ }, "comments_count": { "type": "integer", - "format": "int32" + "format": "int32", + "x-go-type-skip-optional-pointer": false }, "comments_url": { "type": "string" }, "boosts_count": { "type": "integer", - "format": "int32" + "format": "int32", + "x-go-type-skip-optional-pointer": false }, "boosts_url": { "type": "string" }, "position": { "type": "integer", - "format": "int32" + "format": "int32", + "x-go-type-skip-optional-pointer": false }, "parent": { "$ref": "#/components/schemas/RecordingParent" @@ -22329,7 +22332,8 @@ }, "byte_size": { "type": "integer", - "format": "int64" + "format": "int64", + "x-go-type-skip-optional-pointer": false }, "filename": { "type": "string" @@ -22372,6 +22376,16 @@ "items": { "$ref": "#/components/schemas/RichTextAttachment" } + }, + "content": { + "type": "string", + "description": "Rich-text body of the Document variant (uploads/attachments omit it)." + }, + "content_attachments": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RichTextAttachment" + } } } }, diff --git a/typescript/src/generated/schema.d.ts b/typescript/src/generated/schema.d.ts index c8e5f3f97..e06c34a4d 100644 --- a/typescript/src/generated/schema.d.ts +++ b/typescript/src/generated/schema.d.ts @@ -3503,6 +3503,9 @@ export interface components { /** @description Rich-text description (upload/document variants). */ description?: string; description_attachments?: components["schemas"]["RichTextAttachment"][]; + /** @description Rich-text body of the Document variant (uploads/attachments omit it). */ + content?: string; + content_attachments?: components["schemas"]["RichTextAttachment"][]; }; /** @enum {string} */ FirstWeekDay: "Sunday" | "Monday" | "Tuesday" | "Wednesday" | "Thursday" | "Friday" | "Saturday"; diff --git a/typescript/src/index.ts b/typescript/src/index.ts index 6b07fbcec..633da6d04 100644 --- a/typescript/src/index.ts +++ b/typescript/src/index.ts @@ -350,6 +350,13 @@ export { // Everything aggregates service - generated export { EverythingService, + type EverythingFile, + type EverythingMessagesEverythingOptions, + type EverythingCommentsEverythingOptions, + type EverythingCheckinsEverythingOptions, + type EverythingForwardsEverythingOptions, + type EverythingBoostsEverythingOptions, + type EverythingFilesEverythingOptions, } from "./generated/services/everything.js"; // Search & Reports services - generated From 8c2122d1275a4580a5f881397a369a0682b00384 Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Sat, 25 Jul 2026 15:23:46 -0700 Subject: [PATCH 5/5] Fix GetEverythingCheckins doc first line and non-idempotent count (148) The first /// line of GetEverythingCheckins ended mid-sentence with a trailing comma; the TS generator emits only line 1 into method docs, so it produced an incomplete sentence. Make line 1 a complete sentence. Also correct the non-idempotent operation count to 148 (217 total - 69 idempotent) in SPEC.md. --- SPEC.md | 2 +- .../kotlin/com/basecamp/sdk/generated/services/everything.kt | 2 +- openapi.json | 2 +- ruby/lib/basecamp/generated/services/everything_service.rb | 2 +- spec/basecamp.smithy | 4 ++-- typescript/src/generated/openapi-stripped.json | 2 +- typescript/src/generated/schema.d.ts | 4 ++-- typescript/src/generated/services/everything.ts | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/SPEC.md b/SPEC.md index d294e2b37..731ef9283 100644 --- a/SPEC.md +++ b/SPEC.md @@ -1550,7 +1550,7 @@ Every operation has a `retry` block, including non-idempotent POSTs. For non-ide - Total operations: 217 - Idempotent: 69 (flagged with `idempotent: true`) -- Non-idempotent: 140 (no `idempotent` field, or not present) +- Non-idempotent: 148 (no `idempotent` field, or not present) - All operations use `retry_on: [429, 503]` --- diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/services/everything.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/services/everything.kt index d99f78d12..98f12f04f 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/services/everything.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/services/everything.kt @@ -55,7 +55,7 @@ class EverythingService(client: AccountClient) : BaseService(client) { } /** - * Get every automatic check-in answer across all accessible projects, + * Get every automatic check-in answer across all accessible projects, newest-first. * @param options Optional query parameters and pagination control */ suspend fun everythingCheckins(options: GetEverythingCheckinsOptions? = null): ListResult { diff --git a/openapi.json b/openapi.json index ddf879417..429aa7229 100644 --- a/openapi.json +++ b/openapi.json @@ -5587,7 +5587,7 @@ }, "/{accountId}/checkins.json": { "get": { - "description": "Get every automatic check-in answer across all accessible projects,\nnewest-first (paginated). Each item embeds its `bucket`.", + "description": "Get every automatic check-in answer across all accessible projects, newest-first.\nPaginated; each item embeds its `bucket`.", "operationId": "GetEverythingCheckins", "parameters": [ { diff --git a/ruby/lib/basecamp/generated/services/everything_service.rb b/ruby/lib/basecamp/generated/services/everything_service.rb index 9edb61eaa..fd4d97e88 100644 --- a/ruby/lib/basecamp/generated/services/everything_service.rb +++ b/ruby/lib/basecamp/generated/services/everything_service.rb @@ -25,7 +25,7 @@ def get_everything_overdue_cards() end end - # Get every automatic check-in answer across all accessible projects, + # Get every automatic check-in answer across all accessible projects, newest-first. # @param page [Integer, nil] Page number for paginating through results. Defaults to 1. # @return [Enumerator] paginated results def get_everything_checkins(page: nil) diff --git a/spec/basecamp.smithy b/spec/basecamp.smithy index 3fabebcb8..8efadd452 100644 --- a/spec/basecamp.smithy +++ b/spec/basecamp.smithy @@ -8757,8 +8757,8 @@ structure GetEverythingCommentsOutput { recordings: RecordingList } -/// Get every automatic check-in answer across all accessible projects, -/// newest-first (paginated). Each item embeds its `bucket`. +/// Get every automatic check-in answer across all accessible projects, newest-first. +/// Paginated; each item embeds its `bucket`. @readonly @basecampRetry(maxAttempts: 3, baseDelayMs: 1000, backoff: "exponential", retryOn: [429, 503]) @basecampPagination(style: "link", totalCountHeader: "X-Total-Count", maxPageSize: 50) diff --git a/typescript/src/generated/openapi-stripped.json b/typescript/src/generated/openapi-stripped.json index 7c18d8def..97c53ce0d 100644 --- a/typescript/src/generated/openapi-stripped.json +++ b/typescript/src/generated/openapi-stripped.json @@ -4985,7 +4985,7 @@ }, "/checkins.json": { "get": { - "description": "Get every automatic check-in answer across all accessible projects,\nnewest-first (paginated). Each item embeds its `bucket`.", + "description": "Get every automatic check-in answer across all accessible projects, newest-first.\nPaginated; each item embeds its `bucket`.", "operationId": "GetEverythingCheckins", "parameters": [ { diff --git a/typescript/src/generated/schema.d.ts b/typescript/src/generated/schema.d.ts index e06c34a4d..11023e6de 100644 --- a/typescript/src/generated/schema.d.ts +++ b/typescript/src/generated/schema.d.ts @@ -669,8 +669,8 @@ export interface paths { cookie?: never; }; /** - * @description Get every automatic check-in answer across all accessible projects, - * newest-first (paginated). Each item embeds its `bucket`. + * @description Get every automatic check-in answer across all accessible projects, newest-first. + * Paginated; each item embeds its `bucket`. */ get: operations["GetEverythingCheckins"]; put?: never; diff --git a/typescript/src/generated/services/everything.ts b/typescript/src/generated/services/everything.ts index 48754cd68..9c22a35e9 100644 --- a/typescript/src/generated/services/everything.ts +++ b/typescript/src/generated/services/everything.ts @@ -139,7 +139,7 @@ export class EverythingService extends BaseService { } /** - * Get every automatic check-in answer across all accessible projects, + * Get every automatic check-in answer across all accessible projects, newest-first. * @param options - Optional query parameters * @returns All Recording across all pages, with .meta.totalCount *