From b1fc38469e9fc16b20abf91224633a87af9cfce5 Mon Sep 17 00:00:00 2001 From: Bretton Date: Sun, 19 Oct 2025 23:27:09 +0000 Subject: [PATCH] refactor: update handle generation to use .community. format Update PDS provisioning and Jetstream consumer to generate handles using singular .community. instead of .communities. Changes: - PDSAccountProvisioner: Generate {name}.community.{domain} handles - JetstreamConsumer: Parse and validate new handle format - Update handle extraction logic for consistency Example handle formats: - gardening.community.coves.social - gaming.community.coves.social This maintains consistency with the lexicon schema update and aligns with AT Protocol naming conventions. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- internal/atproto/jetstream/community_consumer.go | 10 +++++----- internal/core/communities/pds_provisioning.go | 16 +++++++++------- 2 file(s) changed, 14 insertion(s)(+), 12 deletion(s)(-) diff --git a/internal/atproto/jetstream/community_consumer.go b/internal/atproto/jetstream/community_consumer.go --- a/internal/atproto/jetstream/community_consumer.go +++ b/internal/atproto/jetstream/community_consumer.go @@ -312,7 +312,7 @@ // Extract domain from community handle // Handle format examples: // - "!gaming@coves.social" → domain: "coves.social" - // - "gaming.communities.coves.social" → domain: "coves.social" + // - "gaming.community.coves.social" → domain: "coves.social" handleDomain := extractDomainFromHandle(handle) if handleDomain == "" { return fmt.Errorf("failed to extract domain from handle: %s", handle) @@ -430,11 +430,11 @@ // extractDomainFromHandle extracts the registrable domain from a community handle // Handles both formats: // - Bluesky-style: "!gaming@coves.social" → "coves.social" -// - DNS-style: "gaming.communities.coves.social" → "coves.social" +// - DNS-style: "gaming.community.coves.social" → "coves.social" // // Uses golang.org/x/net/publicsuffix to correctly handle multi-part TLDs: -// - "gaming.communities.coves.co.uk" → "coves.co.uk" (not "co.uk") -// - "gaming.communities.example.com.au" → "example.com.au" (not "com.au") +// - "gaming.community.coves.co.uk" → "coves.co.uk" (not "co.uk") +// - "gaming.community.example.com.au" → "example.com.au" (not "com.au") func extractDomainFromHandle(handle string) string { // Remove leading ! if present handle = strings.TrimPrefix(handle, "!") @@ -456,7 +456,7 @@ } return "" } - // For DNS-style handles (e.g., "gaming.communities.coves.social") + // For DNS-style handles (e.g., "gaming.community.coves.social") // Extract the registrable domain (eTLD+1) using publicsuffix // This correctly handles multi-part TLDs like .co.uk, .com.au, etc. registrable, err := publicsuffix.EffectiveTLDPlusOne(handle) diff --git a/internal/core/communities/pds_provisioning.go b/internal/core/communities/pds_provisioning.go --- a/internal/core/communities/pds_provisioning.go +++ b/internal/core/communities/pds_provisioning.go @@ -15,7 +15,7 @@ // CommunityPDSAccount represents PDS account credentials for a community type CommunityPDSAccount struct { DID string // Community's DID (owns the repository) - Handle string // Community's handle (e.g., gaming.communities.coves.social) + Handle string // Community's handle (e.g., gaming.community.coves.social) Email string // System email for PDS account Password string // Cleartext password (MUST be encrypted before database storage) AccessToken string // JWT for making API calls as the community @@ -67,13 +67,15 @@ return nil, fmt.Errorf("community name is required") } // 1. Generate unique handle for the community - // Format: {name}.communities.{instance-domain} - // Example: "gaming.communities.coves.social" - handle := fmt.Sprintf("%s.communities.%s", strings.ToLower(communityName), p.instanceDomain) + // Format: {name}.community.{instance-domain} + // Example: "gaming.community.coves.social" + // NOTE: Using SINGULAR "community" to follow atProto lexicon conventions + // (all record types use singular: app.bsky.feed.post, app.bsky.graph.follow, etc.) + handle := fmt.Sprintf("%s.community.%s", strings.ToLower(communityName), p.instanceDomain) // 2. Generate system email for PDS account management // This email is used for account operations, not for user communication - email := fmt.Sprintf("community-%s@communities.%s", strings.ToLower(communityName), p.instanceDomain) + email := fmt.Sprintf("community-%s@community.%s", strings.ToLower(communityName), p.instanceDomain) // 3. Generate secure random password (32 characters) // This password is never shown to users - it's for Coves to authenticate as the community @@ -116,8 +118,8 @@ // when access/refresh tokens expire (90-day window on refresh tokens) // The repository layer handles encryption using pgp_sym_encrypt() return &CommunityPDSAccount{ DID: output.Did, // The community's DID (PDS-generated) - Handle: output.Handle, // e.g., gaming.communities.coves.social - Email: email, // community-gaming@communities.coves.social + Handle: output.Handle, // e.g., gaming.community.coves.social + Email: email, // community-gaming@community.coves.social Password: password, // Cleartext - will be encrypted by repository AccessToken: output.AccessJwt, // JWT for making API calls RefreshToken: output.RefreshJwt, // For refreshing sessions -- tangled.sh