Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,11 @@ Fizzy uses **two auth strategies** (no OAuth):

## API Surface Inventory

111 operations across the current Smithy-defined API surface:
112 operations across the current Smithy-defined API surface:

| Area | Operations |
|---------|-----------|
| Identity | GetMyIdentity |
| Identity | GetMyIdentity, UpdateMyTimezone |
| Access Tokens | ListAccessTokens, CreateAccessToken, DeleteAccessToken |
| Account | GetAccountSettings, UpdateAccountSettings, GetJoinCode, UpdateJoinCode, ResetJoinCode, UpdateAccountEntropy, CreateAccountExport, GetAccountExport |
| Boards | ListBoards, CreateBoard, GetBoard, ListBoardAccesses, UpdateBoard, DeleteBoard, PublishBoard, UnpublishBoard, UpdateBoardInvolvement, UpdateBoardEntropy, ListStreamCards, ListPostponedCards, ListClosedCards |
Expand Down
13 changes: 13 additions & 0 deletions behavior-model.json
Original file line number Diff line number Diff line change
Expand Up @@ -1304,6 +1304,19 @@
]
}
},
"UpdateMyTimezone": {
"idempotent": true,
"retry": {
"max": 3,
"base_delay_ms": 1000,
"backoff": "exponential",
"retry_on": [
429,
500,
503
]
}
},
"UpdateNotificationSettings": {
"idempotent": true,
"retry": {
Expand Down
6 changes: 4 additions & 2 deletions conformance/runner/ruby/runner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ def dispatch_operation(tc, client)

# Pins
when "ListPins"
client.pins.list
client.pins.list(account_id: account_id)

# Uploads
when "CreateDirectUpload"
Expand Down Expand Up @@ -344,9 +344,11 @@ def dispatch_operation(tc, client)
when "CompleteJoin"
client.sessions.complete_join(**symbolize_body(body))

# Identity (account-independent)
# Identity
when "GetMyIdentity"
client.identity.me
when "UpdateMyTimezone"
client.identity.update_timezone(account_id: account_id, **symbolize_body(body))

# Devices
when "RegisterDevice"
Expand Down
3 changes: 3 additions & 0 deletions conformance/runner/typescript/runner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,9 @@ async function dispatch(
case "GetMyIdentity":
data = await (client as any).identity.me();
break;
case "UpdateMyTimezone":
data = await (client as any).identity.updateTimezone({ timezoneName: body.timezone_name });
break;

// Notifications
case "ListNotifications": {
Expand Down
27 changes: 24 additions & 3 deletions conformance/tests/paths.json
Original file line number Diff line number Diff line change
Expand Up @@ -211,10 +211,11 @@
"tags": ["paths"]
},
{
"name": "ListPins has no account prefix",
"name": "ListPins uses account-scoped my path",
"operation": "ListPins",
"method": "GET",
"path": "/my/pins.json",
"path": "/{accountId}/my/pins.json",
"pathParams": { "accountId": "999" },
"mockResponses": [
{
"status": 200,
Expand All @@ -223,7 +224,27 @@
}
],
"assertions": [
{ "type": "requestPath", "expected": "/my/pins.json" },
{ "type": "requestPath", "expected": "/999/my/pins.json" },
{ "type": "noError" }
],
"tags": ["paths"]
},
{
"name": "UpdateMyTimezone uses account-scoped my path",
"operation": "UpdateMyTimezone",
"method": "PATCH",
"path": "/{accountId}/my/timezone.json",
"pathParams": { "accountId": "999" },
"requestBody": { "timezone_name": "America/New_York" },
"mockResponses": [
{
"status": 204,
"headers": {},
"body": null
}
],
"assertions": [
{ "type": "requestPath", "expected": "/999/my/timezone.json" },
{ "type": "noError" }
],
"tags": ["paths"]
Expand Down
20 changes: 20 additions & 0 deletions conformance/tests/request-shapes.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,26 @@
],
"tags": ["request-shapes"]
},
{
"name": "UpdateMyTimezone sends timezone_name field",
"operation": "UpdateMyTimezone",
"method": "PATCH",
"path": "/{accountId}/my/timezone.json",
"pathParams": { "accountId": "999" },
"requestBody": { "timezone_name": "America/New_York" },
"mockResponses": [
{
"status": 204,
"headers": {},
"body": null
}
],
"assertions": [
{ "type": "requestBodyField", "expected": "timezone_name" },
{ "type": "noError" }
],
"tags": ["request-shapes"]
},
{
"name": "CreateCardReaction sends content field",
"operation": "CreateCardReaction",
Expand Down
2 changes: 2 additions & 0 deletions go.work.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
2 changes: 2 additions & 0 deletions go/cmd/generate-services/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ func responseTypeName(refName string, schemas map[string]json.RawMessage) (goTyp
// whose service cannot be derived from suffix matching.
var operationServiceOverrides = map[string]string{
"GetMyIdentity": "Identity",
"UpdateMyTimezone": "Identity",
"CreateDirectUpload": "Uploads",
"RedeemMagicLink": "Sessions",
"CompleteJoin": "Sessions",
Expand Down Expand Up @@ -257,6 +258,7 @@ func deriveServiceName(opID string) string {
// follow simple prefix-stripping.
var methodNameOverrides = map[string]string{
"GetMyIdentity": "GetMyIdentity",
"UpdateMyTimezone": "UpdateTimezone",
"RedeemMagicLink": "RedeemMagicLink",
"CompleteJoin": "CompleteJoin",
"CompleteSignup": "CompleteSignup",
Expand Down
4 changes: 2 additions & 2 deletions go/pkg/fizzy/api-provenance.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"fizzy": {
"repo": "basecamp/fizzy",
"revision": "abd59567e38335fb73da47415d6b7fe68ef48f9c",
"date": "2026-04-14",
"revision": "08395fab0e85da88f40702f8fafe556a55af73b3",
"date": "2026-06-01",
"branch": "main",
"paths": {
"api_docs_index": "docs/api/README.md",
Expand Down
7 changes: 7 additions & 0 deletions go/pkg/fizzy/identity_service.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

49 changes: 49 additions & 0 deletions go/pkg/fizzy/identity_service_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package fizzy

import (
"context"
"net/http"
"net/http/httptest"
"testing"

"github.com/basecamp/fizzy-sdk/go/pkg/generated"
)

func TestIdentityUpdateTimezone(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPatch {
t.Fatalf("method = %s, want PATCH", r.Method)
}
if r.URL.Path != "/999/my/timezone.json" {
t.Fatalf("path = %s, want /999/my/timezone.json", r.URL.Path)
}
w.WriteHeader(http.StatusNoContent)
}))
defer server.Close()

client := NewClient(&Config{BaseURL: server.URL}, &StaticTokenProvider{Token: "test"})
_, err := client.Identity().UpdateTimezone(context.Background(), "999", &generated.UpdateMyTimezoneRequest{TimezoneName: "America/New_York"})
if err != nil {
t.Fatalf("UpdateTimezone: %v", err)
}
}

func TestPinsListUsesAccountScopedPath(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
t.Fatalf("method = %s, want GET", r.Method)
}
if r.URL.Path != "/999/my/pins.json" {
t.Fatalf("path = %s, want /999/my/pins.json", r.URL.Path)
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`[]`))
}))
defer server.Close()

client := NewClient(&Config{BaseURL: server.URL}, &StaticTokenProvider{Token: "test"})
_, _, err := client.ForAccount("999").Pins().List(context.Background())
if err != nil {
t.Fatalf("ListPins: %v", err)
}
}
3 changes: 2 additions & 1 deletion go/pkg/fizzy/operations_registry.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 26 additions & 8 deletions go/pkg/fizzy/url-routes.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,6 @@
},
"params": {}
},
{
"pattern": "/my/pins",
"resource": "unknown",
"operations": {
"GET": "ListPins"
},
"params": {}
},
{
"pattern": "/session",
"resource": "unknown",
Expand Down Expand Up @@ -946,6 +938,32 @@
}
}
},
{
"pattern": "/{accountId}/my/pins",
"resource": "unknown",
"operations": {
"GET": "ListPins"
},
"params": {
"accountId": {
"role": "account",
"type": "string"
}
}
},
{
"pattern": "/{accountId}/my/timezone",
"resource": "unknown",
"operations": {
"PATCH": "UpdateMyTimezone"
},
"params": {
"accountId": {
"role": "account",
"type": "string"
}
}
},
{
"pattern": "/{accountId}/notifications",
"resource": "unknown",
Expand Down
1 change: 1 addition & 0 deletions go/pkg/generated/aliases.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ type UpdateCardRequest = UpdateCardRequestContent
type UpdateColumnRequest = UpdateColumnRequestContent
type UpdateCommentRequest = UpdateCommentRequestContent
type UpdateJoinCodeRequest = UpdateJoinCodeRequestContent
type UpdateMyTimezoneRequest = UpdateMyTimezoneRequestContent
type UpdateNotificationSettingsRequest = UpdateNotificationSettingsRequestContent
type UpdateStepRequest = UpdateStepRequestContent
type UpdateUserRequest = UpdateUserRequestContent
Expand Down
8 changes: 8 additions & 0 deletions go/pkg/generated/types.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,9 @@ suspend fun dispatchOperation(tc: TestCase, account: AccountClient): Any? {

// Identity
"GetMyIdentity" -> account.identity.me()
"UpdateMyTimezone" -> account.identity.updateTimezone(
UpdateMyTimezoneBody(timezoneName = body?.string("timezone_name") ?: error("timezone_name is required"))
Comment thread
robzolkos marked this conversation as resolved.
Outdated
)

// Notifications
"ListNotifications" -> account.notifications.list()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ val TAG_TO_SERVICE = mapOf(
/** Explicit overrides for operations that don't follow suffix patterns. */
private val OPERATION_SERVICE_OVERRIDES = mapOf(
"GetMyIdentity" to "Identity",
"UpdateMyTimezone" to "Identity",
"CreateDirectUpload" to "Uploads",
"RedeemMagicLink" to "Sessions",
"CompleteSignup" to "Sessions",
Expand Down Expand Up @@ -139,6 +140,7 @@ val VERB_PATTERNS = listOf(
*/
val METHOD_NAME_OVERRIDES = mapOf(
"GetMyIdentity" to "me",
"UpdateMyTimezone" to "updateTimezone",
"CloseCard" to "close",
"ReopenCard" to "reopen",
"PostponeCard" to "postpone",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ object Metadata {
"UpdateColumn" to OperationConfig(true, RetryConfig(3, 1000L, "exponential", setOf(429, 500, 503))),
"UpdateComment" to OperationConfig(true, RetryConfig(3, 1000L, "exponential", setOf(429, 500, 503))),
"UpdateJoinCode" to OperationConfig(true, RetryConfig(3, 1000L, "exponential", setOf(429, 500, 503))),
"UpdateMyTimezone" to OperationConfig(true, RetryConfig(3, 1000L, "exponential", setOf(429, 500, 503))),
"UpdateNotificationSettings" to OperationConfig(true, RetryConfig(3, 1000L, "exponential", setOf(429, 500, 503))),
"UpdateStep" to OperationConfig(true, RetryConfig(3, 1000L, "exponential", setOf(429, 500, 503))),
"UpdateUser" to OperationConfig(true, RetryConfig(3, 1000L, "exponential", setOf(429, 500, 503))),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,11 @@ data class RegisterDeviceBody(
val name: String? = null
)

/** Request body for UpdateMyTimezone. */
data class UpdateMyTimezoneBody(
val timezoneName: String
)

/** Request body for CreateAccessToken. */
data class CreateAccessTokenBody(
val description: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,24 @@ class IdentityService(client: AccountClient) : BaseService(client) {
json.decodeFromString<Identity>(body)
}
}

/**
* updateTimezone operation
* @param body Request body
*/
suspend fun updateTimezone(body: UpdateMyTimezoneBody): Unit {
val info = OperationInfo(
service = "Identity",
operation = "UpdateMyTimezone",
resourceType = "my_timezone",
isMutation = true,
boardId = null,
resourceId = null,
)
request(info, {
httpPatch("/my/timezone.json", json.encodeToString(kotlinx.serialization.json.buildJsonObject {
put("timezone_name", kotlinx.serialization.json.JsonPrimitive(body.timezoneName))
}), operationName = info.operation)
}) { Unit }
}
}
Loading
Loading