A community based topic aggregation platform built on atproto

feat(communities): add blocking core domain models and interfaces

Add core domain support for community blocking feature:
- CommunityBlock struct with proper atProto metadata (RecordURI, RecordCID)
- ErrBlockNotFound error constant
- Repository interface methods: BlockCommunity, UnblockCommunity, GetBlock,
GetBlockByURI, ListBlockedCommunities, IsBlocked
- Service interface methods: BlockCommunity, UnblockCommunity,
GetBlockedCommunities, IsBlocked

This establishes the domain layer contracts that will be implemented
in subsequent commits following clean architecture principles.

Changed files
+29
internal
+11
internal/core/communities/community.go
···
ID int `json:"id" db:"id"`
}
+
// CommunityBlock represents a user blocking a community
+
// Block records live in the user's repository (at://user_did/social.coves.community.block/{rkey})
+
type CommunityBlock struct {
+
ID int `json:"id" db:"id"`
+
UserDID string `json:"userDid" db:"user_did"`
+
CommunityDID string `json:"communityDid" db:"community_did"`
+
BlockedAt time.Time `json:"blockedAt" db:"blocked_at"`
+
RecordURI string `json:"recordUri,omitempty" db:"record_uri"`
+
RecordCID string `json:"recordCid,omitempty" db:"record_cid"`
+
}
+
// Membership represents active participation with reputation tracking
type Membership struct {
JoinedAt time.Time `json:"joinedAt" db:"joined_at"`
+4
internal/core/communities/errors.go
···
// ErrSubscriptionNotFound is returned when subscription doesn't exist
ErrSubscriptionNotFound = errors.New("subscription not found")
+
// ErrBlockNotFound is returned when block doesn't exist
+
ErrBlockNotFound = errors.New("block not found")
+
// ErrMembershipNotFound is returned when membership doesn't exist
ErrMembershipNotFound = errors.New("membership not found")
···
func IsNotFound(err error) bool {
return errors.Is(err, ErrCommunityNotFound) ||
errors.Is(err, ErrSubscriptionNotFound) ||
+
errors.Is(err, ErrBlockNotFound) ||
errors.Is(err, ErrMembershipNotFound)
}
+14
internal/core/communities/interfaces.go
···
ListSubscriptions(ctx context.Context, userDID string, limit, offset int) ([]*Subscription, error)
ListSubscribers(ctx context.Context, communityDID string, limit, offset int) ([]*Subscription, error)
+
// Community Blocks
+
BlockCommunity(ctx context.Context, block *CommunityBlock) (*CommunityBlock, error)
+
UnblockCommunity(ctx context.Context, userDID, communityDID string) error
+
GetBlock(ctx context.Context, userDID, communityDID string) (*CommunityBlock, error)
+
GetBlockByURI(ctx context.Context, recordURI string) (*CommunityBlock, error) // For Jetstream delete operations
+
ListBlockedCommunities(ctx context.Context, userDID string, limit, offset int) ([]*CommunityBlock, error)
+
IsBlocked(ctx context.Context, userDID, communityDID string) (bool, error)
+
// Memberships (active participation with reputation)
CreateMembership(ctx context.Context, membership *Membership) (*Membership, error)
GetMembership(ctx context.Context, userDID, communityDID string) (*Membership, error)
···
UnsubscribeFromCommunity(ctx context.Context, userDID, userAccessToken, communityIdentifier string) error
GetUserSubscriptions(ctx context.Context, userDID string, limit, offset int) ([]*Subscription, error)
GetCommunitySubscribers(ctx context.Context, communityIdentifier string, limit, offset int) ([]*Subscription, error)
+
+
// Block operations (write-forward: creates record in user's PDS)
+
BlockCommunity(ctx context.Context, userDID, userAccessToken, communityIdentifier string) (*CommunityBlock, error)
+
UnblockCommunity(ctx context.Context, userDID, userAccessToken, communityIdentifier string) error
+
GetBlockedCommunities(ctx context.Context, userDID string, limit, offset int) ([]*CommunityBlock, error)
+
IsBlocked(ctx context.Context, userDID, communityIdentifier string) (bool, error)
// Membership operations (indexed from firehose, reputation managed internally)
GetMembership(ctx context.Context, userDID, communityIdentifier string) (*Membership, error)