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.

fix post schema

+27 -18
+3 -3
pkg/appview/middleware/registry.go
··· 33 33 // These are set by main.go during startup and copied into NamespaceResolver instances. 34 34 // After initialization, request handling uses the NamespaceResolver's instance fields. 35 35 var ( 36 - globalRefresher *oauth.Refresher 37 - globalDatabase storage.DatabaseMetrics 38 - globalAuthorizer auth.HoldAuthorizer 36 + globalRefresher *oauth.Refresher 37 + globalDatabase storage.DatabaseMetrics 38 + globalAuthorizer auth.HoldAuthorizer 39 39 globalReadmeCache storage.ReadmeCache 40 40 ) 41 41
+1
pkg/hold/oci/xrpc.go
··· 291 291 req.Repository, 292 292 req.Tag, 293 293 req.UserHandle, 294 + req.UserDID, 294 295 manifestDigest, 295 296 totalSize, 296 297 )
+8 -8
pkg/hold/pds/manifest_post.go
··· 13 13 // Includes facets for clickable mentions and links 14 14 func (p *HoldPDS) CreateManifestPost( 15 15 ctx context.Context, 16 - repository, tag, userHandle, digest string, 16 + repository, tag, userHandle, userDID, digest string, 17 17 totalSize int64, 18 18 ) (string, error) { 19 19 now := time.Now() ··· 30 30 text := fmt.Sprintf("@%s just pushed %s\nDigest: %s Size: %s", userHandle, repoWithTag, digestShort, sizeStr) 31 31 32 32 // Create facets for mentions and links 33 - facets := buildFacets(text, userHandle, repoWithTag, appViewURL) 33 + facets := buildFacets(text, userHandle, userDID, repoWithTag, appViewURL) 34 34 35 35 // Create post struct with facets 36 36 post := &bsky.FeedPost{ ··· 60 60 return postURI, nil 61 61 } 62 62 63 - // formatDigest truncates digest to first 7 and last 7 chars 64 - // Example: sha256:abc1234567890...fedcba9876543210 -> sha256:abc1234...9876543 63 + // formatDigest truncates digest to first 10 chars 64 + // Example: sha256:abc1234567890fedcba9876543210 -> sha256:abc1234567... 65 65 func formatDigest(digest string) string { 66 66 if !strings.HasPrefix(digest, "sha256:") { 67 67 return digest // Return as-is if not sha256 68 68 } 69 69 70 70 hash := strings.TrimPrefix(digest, "sha256:") 71 - if len(hash) <= 14 { 71 + if len(hash) <= 10 { 72 72 return digest // Too short to truncate 73 73 } 74 74 75 - return fmt.Sprintf("sha256:%s...%s", hash[:7], hash[len(hash)-7:]) 75 + return fmt.Sprintf("sha256:%s...", hash[:10]) 76 76 } 77 77 78 78 // formatSize converts bytes to human-readable format ··· 98 98 99 99 // buildFacets creates mention and link facets for rich text 100 100 // IMPORTANT: Byte offsets must be calculated for UTF-8 encoded text 101 - func buildFacets(text, userHandle, repoWithTag, appViewURL string) []*bsky.RichtextFacet { 101 + func buildFacets(text, userHandle, userDID, repoWithTag, appViewURL string) []*bsky.RichtextFacet { 102 102 facets := []*bsky.RichtextFacet{} 103 103 104 104 // Find mention: "@alice.bsky.social" ··· 117 117 Features: []*bsky.RichtextFacet_Features_Elem{ 118 118 { 119 119 RichtextFacet_Mention: &bsky.RichtextFacet_Mention{ 120 - Did: "", // Will be resolved by Bluesky from handle 120 + Did: userDID, 121 121 }, 122 122 }, 123 123 },
+15 -7
pkg/hold/pds/manifest_post_test.go
··· 16 16 { 17 17 name: "standard sha256 digest", 18 18 digest: "sha256:abc1234567890fedcba9876543210", 19 - expected: "sha256:abc1234...6543210", // Last 7 chars of hash 19 + expected: "sha256:abc1234567...", // First 10 chars 20 20 }, 21 21 { 22 22 name: "short digest (no truncation)", ··· 31 31 { 32 32 name: "real sha256 digest", 33 33 digest: "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f", 34 - expected: "sha256:e692418...7fc331f", // Last 7 chars are "7fc331f" 34 + expected: "sha256:e692418e4c...", // First 10 chars 35 35 }, 36 36 } 37 37 ··· 108 108 name string 109 109 text string 110 110 userHandle string 111 + userDID string 111 112 repoWithTag string 112 113 appViewURL string 113 114 wantFacets int // number of facets expected ··· 116 117 name: "standard post with mention and link", 117 118 text: "@alice.bsky.social just pushed myapp:latest\nDigest: sha256:abc...def Size: 12.2 MB", 118 119 userHandle: "alice.bsky.social", 120 + userDID: "did:plc:alice123", 119 121 repoWithTag: "myapp:latest", 120 122 appViewURL: "https://atcr.io/r/alice.bsky.social/myapp", 121 123 wantFacets: 2, ··· 124 126 name: "no matches found", 125 127 text: "random text", 126 128 userHandle: "alice.bsky.social", 129 + userDID: "did:plc:alice123", 127 130 repoWithTag: "myapp:latest", 128 131 appViewURL: "https://atcr.io/r/alice.bsky.social/myapp", 129 132 wantFacets: 0, ··· 132 135 name: "only mention found", 133 136 text: "@alice.bsky.social did something", 134 137 userHandle: "alice.bsky.social", 138 + userDID: "did:plc:alice123", 135 139 repoWithTag: "myapp:latest", 136 140 appViewURL: "https://atcr.io/r/alice.bsky.social/myapp", 137 141 wantFacets: 1, ··· 140 144 141 145 for _, tt := range tests { 142 146 t.Run(tt.name, func(t *testing.T) { 143 - facets := buildFacets(tt.text, tt.userHandle, tt.repoWithTag, tt.appViewURL) 147 + facets := buildFacets(tt.text, tt.userHandle, tt.userDID, tt.repoWithTag, tt.appViewURL) 144 148 145 149 if len(facets) != tt.wantFacets { 146 150 t.Errorf("buildFacets() returned %d facets, want %d", len(facets), tt.wantFacets) ··· 183 187 // Test that byte offsets are correctly calculated 184 188 text := "@alice.bsky.social just pushed myapp:latest" 185 189 userHandle := "alice.bsky.social" 190 + userDID := "did:plc:alice123" 186 191 repoWithTag := "myapp:latest" 187 192 appViewURL := "https://atcr.io/r/alice.bsky.social/myapp" 188 193 189 - facets := buildFacets(text, userHandle, repoWithTag, appViewURL) 194 + facets := buildFacets(text, userHandle, userDID, repoWithTag, appViewURL) 190 195 191 196 if len(facets) != 2 { 192 197 t.Fatalf("expected 2 facets, got %d", len(facets)) ··· 235 240 // Test with Unicode characters to ensure byte offsets work correctly 236 241 text := "@alice.bsky.social just pushed 🚀myapp:latest" 237 242 userHandle := "alice.bsky.social" 243 + userDID := "did:plc:alice123" 238 244 repoWithTag := "🚀myapp:latest" // Note: emoji is multi-byte 239 245 appViewURL := "https://atcr.io/r/alice.bsky.social/myapp" 240 246 241 - facets := buildFacets(text, userHandle, repoWithTag, appViewURL) 247 + facets := buildFacets(text, userHandle, userDID, repoWithTag, appViewURL) 242 248 243 249 if len(facets) != 2 { 244 250 t.Fatalf("expected 2 facets, got %d", len(facets)) ··· 263 269 // Ensure facets don't overlap 264 270 text := "@alice.bsky.social just pushed myapp:latest" 265 271 userHandle := "alice.bsky.social" 272 + userDID := "did:plc:alice123" 266 273 repoWithTag := "myapp:latest" 267 274 appViewURL := "https://atcr.io/r/alice.bsky.social/myapp" 268 275 269 - facets := buildFacets(text, userHandle, repoWithTag, appViewURL) 276 + facets := buildFacets(text, userHandle, userDID, repoWithTag, appViewURL) 270 277 271 278 if len(facets) != 2 { 272 279 t.Fatalf("expected 2 facets, got %d", len(facets)) ··· 287 294 repository := "hsm-secrets-operator" 288 295 tag := "latest" 289 296 userHandle := "evan.jarrett.net" 297 + userDID := "did:plc:pddp4xt5lgnv2qsegbzzs4xg" 290 298 digest := "sha256:e692418e4cbaf90ca69d05a66403747baa33ee08806650b51fab815ad7fc331f" 291 299 totalSize := int64(12800000) // ~12.2 MB 292 300 ··· 297 305 text := "@" + userHandle + " just pushed " + repoWithTag + "\nDigest: " + digestShort + " Size: " + sizeStr 298 306 appViewURL := "https://atcr.io/r/" + userHandle + "/" + repository 299 307 300 - facets := buildFacets(text, userHandle, repoWithTag, appViewURL) 308 + facets := buildFacets(text, userHandle, userDID, repoWithTag, appViewURL) 301 309 302 310 // Should have 2 facets: mention and link 303 311 if len(facets) != 2 {