A container registry that uses the AT Protocol for manifest storage and S3 for blob storage.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at codeberg-source 131 lines 4.4 kB view raw
1package atproto 2 3import ( 4 "context" 5 "fmt" 6 "strings" 7 8 "github.com/bluesky-social/indigo/atproto/syntax" 9) 10 11// ResolveHoldURL converts a hold identifier (DID or URL) to an HTTP/HTTPS URL 12// Handles both formats for backward compatibility: 13// - DID format: did:web:hold01.atcr.io → https://hold01.atcr.io 14// - DID with port: did:web:172.28.0.3:8080 → http://172.28.0.3:8080 15// - URL format: https://hold.example.com → https://hold.example.com (passthrough) 16func ResolveHoldURL(holdIdentifier string) string { 17 // If it's already a URL (has scheme), return as-is 18 if strings.HasPrefix(holdIdentifier, "http://") || strings.HasPrefix(holdIdentifier, "https://") { 19 return holdIdentifier 20 } 21 22 // If it's a DID, convert to URL 23 if after, ok := strings.CutPrefix(holdIdentifier, "did:web:"); ok { 24 hostname := after 25 26 // Use HTTP for localhost/IP addresses with ports, HTTPS for domains 27 if strings.Contains(hostname, ":") || 28 strings.Contains(hostname, "127.0.0.1") || 29 strings.Contains(hostname, "localhost") || 30 // Check if it's an IP address (contains only digits and dots in first part) 31 (len(hostname) > 0 && hostname[0] >= '0' && hostname[0] <= '9') { 32 return "http://" + hostname 33 } 34 return "https://" + hostname 35 } 36 37 // Fallback: assume it's a hostname and use HTTPS 38 return "https://" + holdIdentifier 39} 40 41// ResolveDIDToPDS resolves a DID to its PDS endpoint. 42// Uses the shared identity directory with cache TTL and event-driven invalidation. 43func ResolveDIDToPDS(ctx context.Context, did string) (string, error) { 44 directory := GetDirectory() 45 didParsed, err := syntax.ParseDID(did) 46 if err != nil { 47 return "", fmt.Errorf("invalid DID: %w", err) 48 } 49 50 ident, err := directory.LookupDID(ctx, didParsed) 51 if err != nil { 52 return "", fmt.Errorf("failed to resolve DID: %w", err) 53 } 54 55 pdsEndpoint := ident.PDSEndpoint() 56 if pdsEndpoint == "" { 57 return "", fmt.Errorf("no PDS endpoint found for DID") 58 } 59 60 return pdsEndpoint, nil 61} 62 63// ResolveIdentity resolves an ATProto identifier (handle or DID) to DID, handle, and PDS endpoint. 64// Uses the shared identity directory with cache TTL and event-driven invalidation. 65// 66// If the handle is invalid (handle.invalid), it returns the DID as the handle for display purposes. 67// Returns: did, handle, pdsEndpoint, error 68func ResolveIdentity(ctx context.Context, identifier string) (string, string, string, error) { 69 directory := GetDirectory() 70 atID, err := syntax.ParseAtIdentifier(identifier) 71 if err != nil { 72 return "", "", "", fmt.Errorf("invalid identifier %q: %w", identifier, err) 73 } 74 75 ident, err := directory.Lookup(ctx, *atID) 76 if err != nil { 77 return "", "", "", fmt.Errorf("failed to resolve identity %q: %w", identifier, err) 78 } 79 80 did := ident.DID.String() 81 handle := ident.Handle.String() 82 pdsEndpoint := ident.PDSEndpoint() 83 84 // If handle is invalid, use DID as display name 85 if handle == "handle.invalid" || handle == "" { 86 handle = did 87 } 88 89 // PDS endpoint is required for XRPC calls 90 if pdsEndpoint == "" { 91 return "", "", "", fmt.Errorf("no PDS endpoint found for identifier %q", identifier) 92 } 93 94 return did, handle, pdsEndpoint, nil 95} 96 97// ResolveHandleToDID resolves a handle or DID to just the DID. 98// Uses the shared identity directory with cache TTL and event-driven invalidation. 99// This is useful when you only need the DID and don't care about handle/PDS. 100func ResolveHandleToDID(ctx context.Context, identifier string) (string, error) { 101 directory := GetDirectory() 102 atID, err := syntax.ParseAtIdentifier(identifier) 103 if err != nil { 104 return "", fmt.Errorf("invalid identifier: %w", err) 105 } 106 107 ident, err := directory.Lookup(ctx, *atID) 108 if err != nil { 109 return "", err 110 } 111 112 return ident.DID.String(), nil 113} 114 115// InvalidateIdentity purges cached identity data for a DID or handle. 116// This should be called when identity changes are detected (e.g., via Jetstream events) 117// to ensure the cache is refreshed on the next lookup. 118// 119// Use cases: 120// - Handle changes (identity events from Jetstream) 121// - Account deactivation/migration (account events from Jetstream) 122// - PDS migrations (deactivation followed by reactivation at new PDS) 123func InvalidateIdentity(ctx context.Context, identifier string) error { 124 directory := GetDirectory() 125 atID, err := syntax.ParseAtIdentifier(identifier) 126 if err != nil { 127 return fmt.Errorf("invalid identifier for cache invalidation: %w", err) 128 } 129 130 return directory.Purge(ctx, *atID) 131}