Files
at-container-registry/pkg/appview/holdclient/tier_update.go
T
2026-05-16 19:39:57 -05:00

91 lines
2.7 KiB
Go

// Package holdclient provides client functions for the appview to call hold XRPC endpoints.
package holdclient
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"log/slog"
"net/http"
"strings"
"atcr.io/pkg/atproto"
"atcr.io/pkg/auth"
"github.com/bluesky-social/indigo/atproto/atcrypto"
)
// UpdateCrewTierOnHold calls io.atcr.hold.updateCrewTier on a specific hold.
// It signs a short-lived JWT with the appview's P-256 key and sends the tier update request.
func UpdateCrewTierOnHold(ctx context.Context, holdDID, holdURL, userDID string, tierRank int, privateKey *atcrypto.PrivateKeyP256, appviewDID string) error {
// Sign appview service token
token, err := auth.CreateAppviewServiceToken(privateKey, appviewDID, holdDID, userDID)
if err != nil {
return fmt.Errorf("failed to create appview token: %w", err)
}
// Build request body
body, err := json.Marshal(map[string]any{
"userDid": userDID,
"tierRank": tierRank,
})
if err != nil {
return fmt.Errorf("failed to marshal request: %w", err)
}
// Build URL
url := strings.TrimSuffix(holdURL, "/") + atproto.HoldUpdateCrewTier
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(body))
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return fmt.Errorf("failed to call updateCrewTier on %s: %w", holdDID, err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
respBody, _ := io.ReadAll(resp.Body)
return fmt.Errorf("updateCrewTier on %s returned %d: %s", holdDID, resp.StatusCode, string(respBody))
}
return nil
}
// UpdateCrewTierOnAllHolds pushes a tier update to all managed holds.
// It resolves each hold DID to a URL and calls updateCrewTier.
// Errors are logged but do not cause the function to fail — best effort.
func UpdateCrewTierOnAllHolds(ctx context.Context, managedHolds []string, userDID string, tierRank int, privateKey *atcrypto.PrivateKeyP256, appviewDID string) {
for _, holdDID := range managedHolds {
holdURL, err := atproto.ResolveHoldDIDToURL(ctx, holdDID)
if err != nil {
slog.Warn("Could not resolve hold DID to URL, skipping",
"holdDID", holdDID,
"error", err,
)
continue
}
if err := UpdateCrewTierOnHold(ctx, holdDID, holdURL, userDID, tierRank, privateKey, appviewDID); err != nil {
slog.Error("Failed to update crew tier on hold",
"holdDID", holdDID,
"userDID", userDID,
"tierRank", tierRank,
"error", err,
)
} else {
slog.Info("Updated crew tier on hold",
"holdDID", holdDID,
"userDID", userDID,
"tierRank", tierRank,
)
}
}
}