From 34d6a22260d4c63fa5037ea7a192195e74b6f531 Mon Sep 17 00:00:00 2001 From: Auston Bunsen Date: Wed, 15 Apr 2026 14:37:33 -0400 Subject: [PATCH] Update card-template-pairs URL and add create endpoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rails PR #1396 renamed /v1/console/pass-template-pairs to /v1/console/card-template-pairs and the JSON response key from pass_template_pairs to card_template_pairs. Update the SDK to match. Public API symbols (ListPassTemplatePairs, PassTemplatePair, PassTemplatePairsResponse, ListPassTemplatePairsParams, the PassTemplatePairs response field) are preserved to avoid breaking customer code — only the underlying URL and JSON struct tag change. - URL: /v1/console/pass-template-pairs -> /v1/console/card-template-pairs - JSON tag: pass_template_pairs -> card_template_pairs - Add ex_id fields on PassTemplatePair and TemplateInfo - Add CreatePassTemplatePair(ctx, params) method for POST /v1/console/card-template-pairs - Add CreatePassTemplatePairParams with name, apple_card_template_id, google_card_template_id - Update README feature matrix; add test coverage for list ex_id fields and new create endpoint - Bump client version to 0.3.0 --- README.md | 3 +- accessgrid.go | 3 ++ client/client.go | 2 +- models/models.go | 18 ++++++++++-- services/console.go | 14 ++++++++- services/console_test.go | 63 ++++++++++++++++++++++++++++++++++++---- 6 files changed, 92 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index db318af..67373fd 100644 --- a/README.md +++ b/README.md @@ -666,7 +666,8 @@ Never expose your `secretKey` in source code. Always use environment variables o | PUT /v1/console/card-templates/{id} | `Console.UpdateTemplate()` | Y | | GET /v1/console/card-templates/{id} | `Console.ReadTemplate()` | Y | | GET /v1/console/card-templates/{id}/logs | `Console.EventLog()` | Y | -| GET /v1/console/pass-template-pairs | `Console.ListPassTemplatePairs()` | Y | +| GET /v1/console/card-template-pairs | `Console.ListPassTemplatePairs()` | Y | +| POST /v1/console/card-template-pairs | `Console.CreatePassTemplatePair()` | Y | | POST /v1/console/card-templates/{id}/ios_preflight | `Console.IosPreflight()` | Y | | GET /v1/console/ledger-items | `Console.ListLedgerItems()` | Y | | GET /v1/console/webhooks | `Console.Webhooks.List()` | Y | diff --git a/accessgrid.go b/accessgrid.go index b81c8b8..750f759 100644 --- a/accessgrid.go +++ b/accessgrid.go @@ -96,6 +96,9 @@ type ( // ListPassTemplatePairsParams defines parameters for listing pass template pairs ListPassTemplatePairsParams = models.ListPassTemplatePairsParams + // CreatePassTemplatePairParams defines parameters for creating a pass template pair + CreatePassTemplatePairParams = models.CreatePassTemplatePairParams + // LedgerItemPassTemplate represents a pass template reference within a ledger item's access pass LedgerItemPassTemplate = models.LedgerItemPassTemplate diff --git a/client/client.go b/client/client.go index 6d4f7aa..6da7ea6 100644 --- a/client/client.go +++ b/client/client.go @@ -17,7 +17,7 @@ import ( const ( baseURL = "https://api.accessgrid.com" defaultTimeout = 30 * time.Second - version = "0.2.0" + version = "0.3.0" ) // APIError represents an error returned by the AccessGrid API diff --git a/models/models.go b/models/models.go index 345d1dc..760ae92 100644 --- a/models/models.go +++ b/models/models.go @@ -224,6 +224,7 @@ type Pagination struct { // TemplateInfo represents minimal template info within a PassTemplatePair type TemplateInfo struct { ID string `json:"id"` + ExID string `json:"ex_id"` Name string `json:"name"` Platform string `json:"platform"` } @@ -231,15 +232,18 @@ type TemplateInfo struct { // PassTemplatePair represents a paired iOS and Android template configuration type PassTemplatePair struct { ID string `json:"id"` + ExID string `json:"ex_id"` Name string `json:"name"` CreatedAt time.Time `json:"created_at"` IOSTemplate *TemplateInfo `json:"ios_template"` AndroidTemplate *TemplateInfo `json:"android_template"` } -// PassTemplatePairsResponse represents the response from listing pass template pairs +// PassTemplatePairsResponse represents the response from listing pass template pairs. +// The upstream JSON key is "card_template_pairs"; the Go field name is preserved +// for backward compatibility. type PassTemplatePairsResponse struct { - PassTemplatePairs []PassTemplatePair `json:"pass_template_pairs"` + PassTemplatePairs []PassTemplatePair `json:"card_template_pairs"` Pagination Pagination `json:"pagination"` } @@ -249,6 +253,16 @@ type ListPassTemplatePairsParams struct { PerPage int `json:"per_page,omitempty"` } +// CreatePassTemplatePairParams defines parameters for creating a pass template pair. +// Both referenced card templates must be published (status: ready) and use the same +// protocol. AppleCardTemplateID must reference an Apple (iOS) template and +// GoogleCardTemplateID must reference a Google (Android) template. +type CreatePassTemplatePairParams struct { + Name string `json:"name"` + AppleCardTemplateID string `json:"apple_card_template_id"` + GoogleCardTemplateID string `json:"google_card_template_id"` +} + // LedgerItemPassTemplate represents a pass template reference within a ledger item's access pass type LedgerItemPassTemplate struct { ID string `json:"id"` diff --git a/services/console.go b/services/console.go index e51a102..2f034ad 100644 --- a/services/console.go +++ b/services/console.go @@ -200,7 +200,7 @@ func (s *ConsoleService) ListPassTemplatePairs(ctx context.Context, params model query.Add("per_page", fmt.Sprintf("%d", params.PerPage)) } - u := url.URL{Path: "/v1/console/pass-template-pairs"} + u := url.URL{Path: "/v1/console/card-template-pairs"} if len(query) > 0 { u.RawQuery = query.Encode() } @@ -212,6 +212,18 @@ func (s *ConsoleService) ListPassTemplatePairs(ctx context.Context, params model return &response, nil } +// CreatePassTemplatePair creates a new pass template pair linking an Apple (iOS) +// and Google (Android) card template. Both templates must be published +// (status: ready) and use the same protocol. +func (s *ConsoleService) CreatePassTemplatePair(ctx context.Context, params models.CreatePassTemplatePairParams) (*models.PassTemplatePair, error) { + var response models.PassTemplatePair + err := s.client.Request(ctx, http.MethodPost, "/v1/console/card-template-pairs", params, &response) + if err != nil { + return nil, fmt.Errorf("error creating pass template pair: %w", err) + } + return &response, nil +} + // ListLedgerItems retrieves billing ledger items func (s *ConsoleService) ListLedgerItems(ctx context.Context, params models.ListLedgerItemsParams) (*models.LedgerItemsResponse, error) { var response models.LedgerItemsResponse diff --git a/services/console_test.go b/services/console_test.go index 43ea726..40dde26 100644 --- a/services/console_test.go +++ b/services/console_test.go @@ -78,21 +78,35 @@ func setupConsoleTestServer() (*httptest.Server, *ConsoleService) { "timestamp": "2023-01-01T12:00:00Z" } ]`)) - case "/v1/console/pass-template-pairs": + case "/v1/console/card-template-pairs": + if r.Method == http.MethodPost { + w.WriteHeader(http.StatusCreated) + w.Write([]byte(`{ + "id": "pair_new", + "ex_id": "pair_new", + "name": "New Badge Pair", + "created_at": "2026-04-15T12:00:00Z", + "ios_template": {"id": "tmpl_ios", "ex_id": "tmpl_ios", "name": "iOS Badge", "platform": "apple"}, + "android_template": {"id": "tmpl_android", "ex_id": "tmpl_android", "name": "Android Badge", "platform": "android"} + }`)) + return + } w.Write([]byte(`{ - "pass_template_pairs": [ + "card_template_pairs": [ { "id": "pair_1", + "ex_id": "pair_1", "name": "Employee Badge Pair", "created_at": "2025-01-01T00:00:00Z", - "ios_template": {"id": "tmpl_ios_1", "name": "iOS Badge", "platform": "apple"}, - "android_template": {"id": "tmpl_android_1", "name": "Android Badge", "platform": "android"} + "ios_template": {"id": "tmpl_ios_1", "ex_id": "tmpl_ios_1", "name": "iOS Badge", "platform": "apple"}, + "android_template": {"id": "tmpl_android_1", "ex_id": "tmpl_android_1", "name": "Android Badge", "platform": "android"} }, { "id": "pair_2", + "ex_id": "pair_2", "name": "Contractor Badge Pair", "created_at": "2025-01-02T00:00:00Z", - "ios_template": {"id": "tmpl_ios_2", "name": "iOS Contractor", "platform": "apple"}, + "ios_template": {"id": "tmpl_ios_2", "ex_id": "tmpl_ios_2", "name": "iOS Contractor", "platform": "apple"}, "android_template": null } ], @@ -381,7 +395,7 @@ func TestConsoleService_ListPassTemplatePairs_WithPagination(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { capturedURL = r.URL.String() w.Header().Set("Content-Type", "application/json") - w.Write([]byte(`{"pass_template_pairs": [], "pagination": {"current_page": 2, "total_pages": 5}}`)) + w.Write([]byte(`{"card_template_pairs": [], "pagination": {"current_page": 2, "total_pages": 5}}`)) })) defer server.Close() @@ -413,6 +427,43 @@ func TestConsoleService_ListPassTemplatePairs_WithPagination(t *testing.T) { if !strings.Contains(capturedURL, "per_page=10") { t.Errorf("expected per_page=10 in URL, got %s", capturedURL) } + if !strings.Contains(capturedURL, "/v1/console/card-template-pairs") { + t.Errorf("expected path /v1/console/card-template-pairs, got %s", capturedURL) + } +} + +func TestConsoleService_CreatePassTemplatePair(t *testing.T) { + server, service := setupConsoleTestServer() + defer server.Close() + + ctx := context.Background() + pair, err := service.CreatePassTemplatePair(ctx, models.CreatePassTemplatePairParams{ + Name: "New Badge Pair", + AppleCardTemplateID: "tmpl_ios", + GoogleCardTemplateID: "tmpl_android", + }) + if err != nil { + t.Fatalf("CreatePassTemplatePair() error = %v", err) + } + + if pair.ID != "pair_new" { + t.Errorf("pair.ID = %v, want pair_new", pair.ID) + } + if pair.ExID != "pair_new" { + t.Errorf("pair.ExID = %v, want pair_new", pair.ExID) + } + if pair.Name != "New Badge Pair" { + t.Errorf("pair.Name = %v, want New Badge Pair", pair.Name) + } + if pair.IOSTemplate == nil || pair.IOSTemplate.Platform != "apple" { + t.Errorf("pair.IOSTemplate = %+v, want apple platform", pair.IOSTemplate) + } + if pair.AndroidTemplate == nil || pair.AndroidTemplate.Platform != "android" { + t.Errorf("pair.AndroidTemplate = %+v, want android platform", pair.AndroidTemplate) + } + if pair.IOSTemplate.ExID != "tmpl_ios" { + t.Errorf("pair.IOSTemplate.ExID = %v, want tmpl_ios", pair.IOSTemplate.ExID) + } } // --- Ledger Items ---