From e86ecba292420042a004e8daa18323ecd740d367 Mon Sep 17 00:00:00 2001 From: "Stanko K.R." Date: Wed, 26 Aug 2026 18:42:20 +0200 Subject: [PATCH] Model the Imbox's Previously Seen postings: GetImboxSeen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GET /imbox/seen.json answers the box show shape with the Imbox's seen postings ordered by when they were seen, an ordering the Imbox's own pages do not offer. Its next_history_url names /imbox, but the page cursor inside it belongs to the seen ordering — extract the cursor and hand it back to GetImboxSeen, never to GetImbox. --- behavior-model.json | 13 +++ go/pkg/generated/client.gen.go | 202 +++++++++++++++++++++++++++++++++ go/pkg/hey/boxes.go | 29 +++++ go/pkg/hey/url-routes.json | 8 ++ openapi.json | 73 ++++++++++++ spec/excluded-routes.json | 5 - spec/hey.smithy | 17 +++ spec/route-coverage.json | 5 + spec/shape-fingerprint.json | 1 + 9 files changed, 348 insertions(+), 5 deletions(-) diff --git a/behavior-model.json b/behavior-model.json index 11c8e3b..3120de6 100644 --- a/behavior-model.json +++ b/behavior-model.json @@ -681,6 +681,19 @@ ] } }, + "GetImboxSeen": { + "idempotent": false, + "readonly": true, + "retry": { + "backoff": "exponential", + "base_delay_ms": 1000, + "max": 3, + "retry_on": [ + 429, + 503 + ] + } + }, "GetJournalEntry": { "idempotent": false, "readonly": true, diff --git a/go/pkg/generated/client.gen.go b/go/pkg/generated/client.gen.go index 23a921d..98fb3ec 100644 --- a/go/pkg/generated/client.gen.go +++ b/go/pkg/generated/client.gen.go @@ -808,6 +808,11 @@ type GetIdentityResponseContent = Identity // SDK response decoders normalize the nested variant to flat before decoding. type GetImboxResponseContent = BoxShowResponse +// GetImboxSeenResponseContent BoxShowResponse — box detail with postings. +// The API can return fields at root level or nested under a `box` key. +// SDK response decoders normalize the nested variant to flat before decoding. +type GetImboxSeenResponseContent = BoxShowResponse + // GetJournalEntryResponseContent Recording — polymorphic by `type` (CalendarEvent, CalendarTodo, etc.) type GetJournalEntryResponseContent = Recording @@ -1825,6 +1830,11 @@ type GetImboxParams struct { Page *string `form:"page,omitempty" json:"page,omitempty"` } +// GetImboxSeenParams defines parameters for GetImboxSeen. +type GetImboxSeenParams struct { + Page *string `form:"page,omitempty" json:"page,omitempty"` +} + // GetMyClearancesParams defines parameters for GetMyClearances. type GetMyClearancesParams struct { Page *string `form:"page,omitempty" json:"page,omitempty"` @@ -2527,6 +2537,9 @@ type ClientInterface interface { // GetImbox request GetImbox(ctx context.Context, params *GetImboxParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetImboxSeen request + GetImboxSeen(ctx context.Context, params *GetImboxSeenParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // CreateMessageWithBody request with any body CreateMessageWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -3881,6 +3894,16 @@ func (c *Client) GetImbox(ctx context.Context, params *GetImboxParams, reqEditor } +// GetImboxSeen is marked as idempotent and will be retried on transient failures. + +func (c *Client) GetImboxSeen(ctx context.Context, params *GetImboxSeenParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + + return c.doWithRetry(ctx, func() (*http.Request, error) { + return NewGetImboxSeenRequest(c.Server, params) + }, true, "GetImboxSeen", reqEditors...) + +} + // CreateMessageWithBody executes the CreateMessage operation. func (c *Client) CreateMessageWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { @@ -8201,6 +8224,55 @@ func NewGetImboxRequest(server string, params *GetImboxParams) (*http.Request, e return req, nil } +// NewGetImboxSeenRequest generates requests for GetImboxSeen +func NewGetImboxSeenRequest(server string, params *GetImboxSeenParams) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/imbox/seen.json") + 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 != 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 + } 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 +} + // NewCreateMessageRequest calls the generic CreateMessage builder with application/json body func NewCreateMessageRequest(server string, body CreateMessageJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader @@ -10371,6 +10443,7 @@ var operationMetadata = map[string]OperationMetadata{ "GetIdentity": {Idempotent: true, HasSensitiveParams: false}, "UpdateTimeFormat": {Idempotent: true, HasSensitiveParams: false}, "GetImbox": {Idempotent: true, HasSensitiveParams: false}, + "GetImboxSeen": {Idempotent: true, HasSensitiveParams: false}, "CreateMessage": {Idempotent: false, HasSensitiveParams: false}, "GetMessage": {Idempotent: true, HasSensitiveParams: false}, "UpdateMessage": {Idempotent: true, HasSensitiveParams: false}, @@ -11787,6 +11860,13 @@ type ClientWithResponsesInterface interface { // Returns a wrapper object for the known response body format(s). GetImboxWithResponse(ctx context.Context, params *GetImboxParams, reqEditors ...RequestEditorFn) (*GetImboxResponse, error) + // GetImboxSeenWithResponse performs a GET /imbox/seen.json (the `GetImboxSeen` operationId) request. + // + // Get the Imbox's Previously Seen postings. + // + // Returns a wrapper object for the known response body format(s). + GetImboxSeenWithResponse(ctx context.Context, params *GetImboxSeenParams, reqEditors ...RequestEditorFn) (*GetImboxSeenResponse, error) + // CreateMessageWithBodyWithResponse performs a POST /messages.json (the `CreateMessage` operationId) request, // with any type of body and a specified content type. // @@ -17443,6 +17523,68 @@ func (r GetImboxResponse) ContentType() string { return "" } +type GetImboxSeenResponse struct { + Body []byte + HTTPResponse *http.Response + // JSON200 the response for an HTTP 200 `application/json` response + JSON200 *GetImboxSeenResponseContent + // JSON401 the response for an HTTP 401 `application/json` response + JSON401 *UnauthorizedErrorResponseContent + // JSON500 the response for an HTTP 500 `application/json` response + JSON500 *InternalServerErrorResponseContent + // JSON503 the response for an HTTP 503 `application/json` response + JSON503 *ServiceUnavailableErrorResponseContent +} + +// GetJSON200 returns the response for an HTTP 200 `application/json` response +func (r GetImboxSeenResponse) GetJSON200() *GetImboxSeenResponseContent { + return r.JSON200 +} + +// GetJSON401 returns the response for an HTTP 401 `application/json` response +func (r GetImboxSeenResponse) GetJSON401() *UnauthorizedErrorResponseContent { + return r.JSON401 +} + +// GetJSON500 returns the response for an HTTP 500 `application/json` response +func (r GetImboxSeenResponse) GetJSON500() *InternalServerErrorResponseContent { + return r.JSON500 +} + +// GetJSON503 returns the response for an HTTP 503 `application/json` response +func (r GetImboxSeenResponse) GetJSON503() *ServiceUnavailableErrorResponseContent { + return r.JSON503 +} + +// GetBody returns the raw response body bytes +func (r GetImboxSeenResponse) GetBody() []byte { + return r.Body +} + +// Status returns HTTPResponse.Status +func (r GetImboxSeenResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetImboxSeenResponse) 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 GetImboxSeenResponse) ContentType() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Header.Get("Content-Type") + } + return "" +} + type CreateMessageResponse struct { Body []byte HTTPResponse *http.Response @@ -21916,6 +22058,19 @@ func (c *ClientWithResponses) GetImboxWithResponse(ctx context.Context, params * return ParseGetImboxResponse(rsp) } +// GetImboxSeenWithResponse performs a GET /imbox/seen.json (the `GetImboxSeen` operationId) request. +// +// Get the Imbox's Previously Seen postings. +// +// Returns a wrapper object for the known response body format(s). +func (c *ClientWithResponses) GetImboxSeenWithResponse(ctx context.Context, params *GetImboxSeenParams, reqEditors ...RequestEditorFn) (*GetImboxSeenResponse, error) { + rsp, err := c.GetImboxSeen(ctx, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetImboxSeenResponse(rsp) +} + // CreateMessageWithBodyWithResponse performs a POST /messages.json (the `CreateMessage` operationId) request, // with any type of body and a specified content type. // @@ -26902,6 +27057,53 @@ func ParseGetImboxResponse(rsp *http.Response) (*GetImboxResponse, error) { return response, nil } +// ParseGetImboxSeenResponse parses an HTTP response from a GetImboxSeenWithResponse call +func ParseGetImboxSeenResponse(rsp *http.Response) (*GetImboxSeenResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetImboxSeenResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest GetImboxSeenResponseContent + 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 == 500: + var dest InternalServerErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 503: + var dest ServiceUnavailableErrorResponseContent + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON503 = &dest + + } + + return response, nil +} + // ParseCreateMessageResponse parses an HTTP response from a CreateMessageWithResponse call func ParseCreateMessageResponse(rsp *http.Response) (*CreateMessageResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) diff --git a/go/pkg/hey/boxes.go b/go/pkg/hey/boxes.go index 612b24b..430d6e0 100644 --- a/go/pkg/hey/boxes.go +++ b/go/pkg/hey/boxes.go @@ -111,6 +111,35 @@ func (s *BoxesService) GetImbox(ctx context.Context, params *generated.GetImboxP return resp.JSON200, nil } +// GetImboxSeen returns the Imbox's Previously Seen postings, ordered by when +// they were seen (observed_at desc). The response's next_history_url names the +// /imbox route, but its page cursor belongs to the seen scope — extract the +// cursor and feed it back to GetImboxSeen, never to GetImbox. +func (s *BoxesService) GetImboxSeen(ctx context.Context, params *generated.GetImboxSeenParams) (result *generated.BoxShowResponse, err error) { + op := OperationInfo{ + Service: "Boxes", Operation: "GetImboxSeen", + ResourceType: "box", IsMutation: false, + } + if gater, ok := s.client.hooks.(GatingHooks); ok { + if ctx, err = gater.OnOperationGate(ctx, op); err != nil { + return + } + } + start := time.Now() + ctx = s.client.hooks.OnOperationStart(ctx, op) + defer func() { s.client.hooks.OnOperationEnd(ctx, op, err, time.Since(start)) }() + + s.client.initGeneratedClient() + resp, err := s.client.gen.GetImboxSeenWithResponse(ctx, params) + if err != nil { + return nil, err + } + if err = CheckResponse(resp.HTTPResponse); err != nil { + return nil, err + } + return resp.JSON200, nil +} + // GetFeedbox returns the Feed. func (s *BoxesService) GetFeedbox(ctx context.Context, params *generated.GetFeedboxParams) (result *generated.BoxShowResponse, err error) { op := OperationInfo{ diff --git a/go/pkg/hey/url-routes.json b/go/pkg/hey/url-routes.json index 60ed364..749b440 100644 --- a/go/pkg/hey/url-routes.json +++ b/go/pkg/hey/url-routes.json @@ -664,6 +664,14 @@ }, "params": {} }, + { + "pattern": "/imbox/seen", + "resource": "Boxes", + "operations": { + "GET": "GetImboxSeen" + }, + "params": {} + }, { "pattern": "/messages", "resource": "Messages", diff --git a/openapi.json b/openapi.json index ba349c4..6cc8b53 100644 --- a/openapi.json +++ b/openapi.json @@ -6303,6 +6303,76 @@ } } }, + "/imbox/seen.json": { + "get": { + "description": "Get the Imbox's Previously Seen postings", + "operationId": "GetImboxSeen", + "parameters": [ + { + "name": "page", + "in": "query", + "schema": { + "type": "string", + "x-go-type-skip-optional-pointer": false + } + } + ], + "responses": { + "200": { + "description": "GetImboxSeen 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetImboxSeenResponseContent" + } + } + } + }, + "401": { + "description": "UnauthorizedError 401 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedErrorResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + }, + "503": { + "description": "ServiceUnavailableError 503 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ServiceUnavailableErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Boxes" + ], + "x-hey-retry": { + "maxAttempts": 3, + "baseDelayMs": 1000, + "backoff": "exponential", + "retryOn": [ + 429, + 503 + ] + } + } + }, "/messages.json": { "post": { "description": "Create a new message (start a new topic).\nThe acting sender ID must be included; the Go SDK resolves this automatically.\nEvery message is created drafted on HEY's side; without entry.status the server\ndelivers it, while entry.status \"drafted\" leaves it as a draft and answers\n204 with a Location header naming /messages/{entry_id}.", @@ -11951,6 +12021,9 @@ "GetImboxResponseContent": { "$ref": "#/components/schemas/BoxShowResponse" }, + "GetImboxSeenResponseContent": { + "$ref": "#/components/schemas/BoxShowResponse" + }, "GetJournalEntryResponseContent": { "$ref": "#/components/schemas/Recording" }, diff --git a/spec/excluded-routes.json b/spec/excluded-routes.json index cb12d3d..8ca2cfb 100644 --- a/spec/excluded-routes.json +++ b/spec/excluded-routes.json @@ -1704,11 +1704,6 @@ "path": "/imbox/bubbled_up", "reason": "phase-2: imbox management" }, - { - "method": "GET", - "path": "/imbox/seen", - "reason": "phase-2: imbox management" - }, { "method": "GET", "path": "/imbox/unseen", diff --git a/spec/hey.smithy b/spec/hey.smithy index 92bde3d..70a7bd6 100644 --- a/spec/hey.smithy +++ b/spec/hey.smithy @@ -71,6 +71,7 @@ service HEY { ListBoxes GetBox GetImbox + GetImboxSeen GetFeedbox GetTrailbox GetAsidebox @@ -1327,6 +1328,22 @@ structure GetNamedBoxOutput { box: BoxShowResponse } +/// Get the Imbox's Previously Seen postings +@readonly +@http(method: "GET", uri: "/imbox/seen.json") +@tags(["Boxes"]) +@heyRetry(maxAttempts: 3, baseDelayMs: 1000, backoff: "exponential", retryOn: [429, 503]) +operation GetImboxSeen { + input: GetNamedBoxInput + output: GetImboxSeenOutput + errors: [UnauthorizedError, InternalServerError, ServiceUnavailableError] +} + +structure GetImboxSeenOutput { + @required + box: BoxShowResponse +} + /// Get the Feed @readonly @http(method: "GET", uri: "/feedbox.json") diff --git a/spec/route-coverage.json b/spec/route-coverage.json index dbd1798..04b1f9c 100644 --- a/spec/route-coverage.json +++ b/spec/route-coverage.json @@ -254,6 +254,11 @@ "operationId": "GetImbox", "path": "/imbox.json" }, + { + "method": "GET", + "operationId": "GetImboxSeen", + "path": "/imbox/seen.json" + }, { "method": "GET", "operationId": "GetJournalEntry", diff --git a/spec/shape-fingerprint.json b/spec/shape-fingerprint.json index 188c779..78db118 100644 --- a/spec/shape-fingerprint.json +++ b/spec/shape-fingerprint.json @@ -30,6 +30,7 @@ "GetFolder": "d74a2b9f7b1573063d9978189528d485496e7dd9787823a9b02355637a996753", "GetIdentity": "6dc4fc98b5ae474c72ff6ffef6f9b010cfc51dd0cea16d1b58f799d68d2bd1ef", "GetImbox": "b2faa64dcdcd507f1e913e114bf943a4d677a3ecce8ad0a5c3f99d3b7569d229", + "GetImboxSeen": "220ed5bc3bae521f2ff05036665127458b65140b4f377ac3995e6ba151e8bdda", "GetJournalEntry": "f045610c5289ac1d282c60805cc787a4df2d6b2ab8d41a59ba2299a56abab6a4", "GetLaterbox": "6cc7e63b0f9142d966565eae669bbd7e3d0475164e904b277961c657d75da23f", "GetMessage": "449eb645bc61901cd766000b7f7acc22f0432336e3ba1d9f1503f79dda61fe14",