mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-09-02 08:16:57 +00:00
98 lines
2.9 KiB
Go
98 lines
2.9 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
|
|
}
|
|
|
|
// urlFromDIDWeb converts a did:web to its HTTPS URL.
|
|
func urlFromDIDWeb(did string) string {
|
|
if !strings.HasPrefix(did, "did:web:") {
|
|
return ""
|
|
}
|
|
host := strings.TrimPrefix(did, "did:web:")
|
|
host = strings.ReplaceAll(host, "%3A", ":")
|
|
return "https://" + host
|
|
}
|
|
|
|
// 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 := urlFromDIDWeb(holdDID)
|
|
if holdURL == "" {
|
|
slog.Warn("Could not resolve hold DID to URL, skipping", "holdDID", holdDID)
|
|
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,
|
|
)
|
|
}
|
|
}
|
|
}
|