A container registry that uses the AT Protocol for manifest storage and S3 for blob storage. atcr.io
docker container atproto go

clean up duplicate functions

evan.jarrett.net e62ebdaa 4cfe6f22

verified
Changed files
+23 -52
pkg
appview
handlers
holdhealth
jetstream
atproto
auth
oauth
hold
pds
+5 -12
pkg/appview/handlers/api.go
··· 1 1 package handlers 2 2 3 3 import ( 4 - "context" 5 4 "database/sql" 6 5 "encoding/json" 7 6 "errors" ··· 37 36 repository := chi.URLParam(r, "repository") 38 37 39 38 // Resolve owner's handle to DID 40 - ownerDID, err := resolveIdentityToDID(r.Context(), h.Directory, handle) 39 + ownerDID, err := atproto.ResolveHandleToDID(r.Context(), handle) 41 40 if err != nil { 42 41 slog.Warn("Failed to resolve handle for star", "handle", handle, "error", err) 43 42 http.Error(w, fmt.Sprintf("Failed to resolve handle: %v", err), http.StatusBadRequest) ··· 95 94 repository := chi.URLParam(r, "repository") 96 95 97 96 // Resolve owner's handle to DID 98 - ownerDID, err := resolveIdentityToDID(r.Context(), h.Directory, handle) 97 + ownerDID, err := atproto.ResolveHandleToDID(r.Context(), handle) 99 98 if err != nil { 100 99 slog.Warn("Failed to resolve handle for unstar", "handle", handle, "error", err) 101 100 http.Error(w, fmt.Sprintf("Failed to resolve handle: %v", err), http.StatusBadRequest) ··· 156 155 repository := chi.URLParam(r, "repository") 157 156 158 157 // Resolve owner's handle to DID 159 - ownerDID, err := resolveIdentityToDID(r.Context(), h.Directory, handle) 158 + ownerDID, err := atproto.ResolveHandleToDID(r.Context(), handle) 160 159 if err != nil { 161 160 slog.Warn("Failed to resolve handle for check star", "handle", handle, "error", err) 162 161 http.Error(w, fmt.Sprintf("Failed to resolve handle: %v", err), http.StatusBadRequest) ··· 200 199 repository := chi.URLParam(r, "repository") 201 200 202 201 // Resolve owner's handle to DID 203 - ownerDID, err := resolveIdentityToDID(r.Context(), h.Directory, handle) 202 + ownerDID, err := atproto.ResolveHandleToDID(r.Context(), handle) 204 203 if err != nil { 205 204 http.Error(w, "Failed to resolve handle", http.StatusBadRequest) 206 205 return ··· 231 230 digest := chi.URLParam(r, "digest") 232 231 233 232 // Resolve owner's handle to DID 234 - ownerDID, err := resolveIdentityToDID(r.Context(), h.Directory, handle) 233 + ownerDID, err := atproto.ResolveHandleToDID(r.Context(), handle) 235 234 if err != nil { 236 235 http.Error(w, "Failed to resolve handle", http.StatusBadRequest) 237 236 return ··· 253 252 w.Header().Set("Content-Type", "application/json") 254 253 json.NewEncoder(w).Encode(manifest) 255 254 } 256 - 257 - // resolveIdentityToDID is a helper function that resolves a handle or DID to a DID 258 - func resolveIdentityToDID(ctx context.Context, directory identity.Directory, identityStr string) (string, error) { 259 - // Resolve to DID via directory (handles both handles and DIDs) 260 - return atproto.ResolveHandleToDID(ctx, identityStr) 261 - }
+3 -1
pkg/appview/holdhealth/checker_test.go
··· 6 6 "net/http/httptest" 7 7 "testing" 8 8 "time" 9 + 10 + "atcr.io/pkg/atproto" 9 11 ) 10 12 11 13 func TestNewChecker(t *testing.T) { ··· 317 319 318 320 for _, tt := range tests { 319 321 t.Run(tt.name, func(t *testing.T) { 320 - result := normalizeHoldEndpoint(tt.input) 322 + result := atproto.ResolveHoldDIDFromURL(tt.input) 321 323 if result != tt.expected { 322 324 t.Errorf("normalizeHoldEndpoint(%q) = %q, want %q", tt.input, result, tt.expected) 323 325 }
+3 -28
pkg/appview/holdhealth/worker.go
··· 5 5 "database/sql" 6 6 "fmt" 7 7 "log/slog" 8 - "strings" 9 8 "sync" 10 9 "time" 10 + 11 + "atcr.io/pkg/atproto" 11 12 ) 12 13 13 14 // DBQuerier interface for database queries (allows mocking in tests) ··· 129 130 130 131 for _, endpoint := range endpoints { 131 132 // Normalize to canonical DID format 132 - normalizedDID := normalizeHoldEndpoint(endpoint) 133 + normalizedDID := atproto.ResolveHoldDIDFromURL(endpoint) 133 134 134 135 // Skip if we've already seen this normalized DID 135 136 if seen[normalizedDID] { ··· 219 220 220 221 return endpoints, nil 221 222 } 222 - 223 - // normalizeHoldEndpoint converts a hold endpoint (URL or DID) to canonical DID format 224 - // This ensures that different representations of the same hold are deduplicated: 225 - // - http://172.28.0.3:8080 → did:web:172.28.0.3:8080 226 - // - http://hold01.atcr.io → did:web:hold01.atcr.io 227 - // - https://hold01.atcr.io → did:web:hold01.atcr.io 228 - // - did:web:hold01.atcr.io → did:web:hold01.atcr.io (passthrough) 229 - func normalizeHoldEndpoint(endpoint string) string { 230 - // Strip protocol and trailing slashes 231 - normalized := endpoint 232 - normalized = strings.TrimPrefix(normalized, "http://") 233 - normalized = strings.TrimPrefix(normalized, "https://") 234 - normalized = strings.TrimSuffix(normalized, "/") 235 - 236 - // If already a DID, return as-is 237 - if strings.HasPrefix(endpoint, "did:") { 238 - return endpoint 239 - } 240 - 241 - // Extract hostname (remove path if present) 242 - parts := strings.Split(normalized, "/") 243 - hostname := parts[0] 244 - 245 - // Convert to did:web 246 - return "did:web:" + hostname 247 - }
+2 -2
pkg/appview/jetstream/processor.go
··· 25 25 // useCache: true for Worker (live streaming), false for Backfill (batch processing) 26 26 func NewProcessor(database *sql.DB, useCache bool) *Processor { 27 27 p := &Processor{ 28 - db: database, 29 - useCache: useCache, 28 + db: database, 29 + useCache: useCache, 30 30 } 31 31 32 32 if useCache {
-1
pkg/appview/ui_test.go
··· 525 525 526 526 // Test that all expected templates are loaded 527 527 expectedTemplates := []string{ 528 - "base.html", 529 528 "nav", 530 529 "repo-card", 531 530 "repository",
+6 -3
pkg/atproto/lexicon.go
··· 406 406 } 407 407 408 408 // ResolveHoldDIDFromURL converts a hold endpoint URL to a did:web DID 409 - // For did:web holds: https://hold01.atcr.io → did:web:hold01.atcr.io 410 - // If input is already a DID, returns it as-is 409 + // This ensures that different representations of the same hold are deduplicated: 410 + // - http://172.28.0.3:8080 → did:web:172.28.0.3:8080 411 + // - http://hold01.atcr.io → did:web:hold01.atcr.io 412 + // - https://hold01.atcr.io → did:web:hold01.atcr.io 413 + // - did:web:hold01.atcr.io → did:web:hold01.atcr.io (passthrough) 411 414 func ResolveHoldDIDFromURL(holdURL string) string { 412 415 // Handle empty URLs 413 416 if holdURL == "" { ··· 415 418 } 416 419 417 420 // If already a DID, return as-is 418 - if strings.HasPrefix(holdURL, "did:") { 421 + if IsDID(holdURL) { 419 422 return holdURL 420 423 } 421 424
+2 -3
pkg/auth/oauth/client.go
··· 20 20 type App struct { 21 21 clientApp *oauth.ClientApp 22 22 baseURL string 23 - directory identity.Directory 24 23 } 25 24 26 25 // NewApp creates a new OAuth app for ATCR with default scopes ··· 32 31 func NewAppWithScopes(baseURL string, store oauth.ClientAuthStore, scopes []string) (*App, error) { 33 32 config := NewClientConfigWithScopes(baseURL, scopes) 34 33 clientApp := oauth.NewClientApp(&config, store) 34 + clientApp.Dir = atproto.GetDirectory() 35 35 36 36 return &App{ 37 37 clientApp: clientApp, 38 38 baseURL: baseURL, 39 - directory: atproto.GetDirectory(), 40 39 }, nil 41 40 } 42 41 ··· 102 101 103 102 // Directory returns the identity directory used by the OAuth app 104 103 func (a *App) Directory() identity.Directory { 105 - return a.directory 104 + return a.clientApp.Dir 106 105 } 107 106 108 107 // ClientIDWithScopes generates a client ID with custom scopes
+2 -2
pkg/hold/pds/xrpc.go
··· 263 263 264 264 // Normalize actor to DID 265 265 actorDID := actor 266 - if !strings.HasPrefix(actor, "did:") { 266 + if !atproto.IsDID(actor) { 267 267 // It's a handle, resolve to DID 268 268 expectedHandle := strings.TrimPrefix(h.pds.DID(), "did:web:") 269 269 if actor == expectedHandle { ··· 306 306 for _, actor := range actors { 307 307 // Normalize actor to DID 308 308 actorDID := actor 309 - if !strings.HasPrefix(actor, "did:") { 309 + if !atproto.IsDID(actor) { 310 310 // It's a handle, check if it matches 311 311 if actor == expectedHandle { 312 312 actorDID = h.pds.DID()