···
return nil, NewValidationError("identifier", "must be a DID or handle")
+
// GetByDID retrieves a community by its DID
+
// Exported for use by post service when validating community references
+
func (s *communityService) GetByDID(ctx context.Context, did string) (*Community, error) {
+
return nil, ErrInvalidInput
+
if !strings.HasPrefix(did, "did:") {
+
return nil, NewValidationError("did", "must be a valid DID")
+
return s.repo.GetByDID(ctx, did)
// UpdateCommunity updates a community via write-forward to PDS
func (s *communityService) UpdateCommunity(ctx context.Context, req UpdateCommunityRequest) (*Community, error) {
if req.CommunityDID == "" {
···
// CRITICAL: Ensure fresh PDS access token before write operation
// Community PDS tokens expire every ~2 hours and must be refreshed
+
existing, err = s.EnsureFreshToken(ctx, existing)
return nil, fmt.Errorf("failed to ensure fresh credentials: %w", err)
···
// ensureFreshToken checks if a community's access token needs refresh and updates if needed
// Returns updated community with fresh credentials (or original if no refresh needed)
// Thread-safe: Uses per-community mutex to prevent concurrent refresh attempts
+
// EnsureFreshToken ensures the community's PDS access token is valid
+
// Exported for use by post service when writing posts to community repos
+
func (s *communityService) EnsureFreshToken(ctx context.Context, community *Community) (*Community, error) {
// Get or create mutex for this specific community DID
mutex := s.getOrCreateRefreshMutex(community.DID)
···
// Following Bluesky's pattern with Coves extensions:
// Accepts (like Bluesky's at-identifier):
+
// 1. DID: did:plc:abc123 (pass through)
+
// 2. Canonical handle: gardening.community.coves.social (atProto standard)
+
// 3. At-identifier: @gardening.community.coves.social (strip @ prefix)
// Coves-specific extensions:
+
// 4. Scoped format: !gardening@coves.social (parse and resolve)
func (s *communityService) ResolveCommunityIdentifier(ctx context.Context, identifier string) (string, error) {
···
// 3. At-identifier format: @handle (Bluesky standard - strip @ prefix)
+
identifier = strings.TrimPrefix(identifier, "@")
// 4. Canonical handle: name.community.instance.com (Bluesky standard)
if strings.Contains(identifier, ".") {
···
// resolveScopedIdentifier handles Coves-specific !name@instance format
+
// !gardening@coves.social -> gardening.community.coves.social
func (s *communityService) resolveScopedIdentifier(ctx context.Context, scoped string) (string, error) {
scoped = strings.TrimPrefix(scoped, "!")