A community based topic aggregation platform built on atproto

feat(server): wire aggregator system into application

Integrate aggregator components into server initialization and
register XRPC endpoints.

Changes to cmd/server/main.go:
- Initialize aggregator repository
- Initialize aggregator service (with community service dependency)
- Update post service to include aggregator service
- Register aggregator XRPC routes (3 query endpoints)
- Start aggregator Jetstream consumer in background goroutine
- Add comprehensive startup logging

Server startup output:
✅ Aggregator service initialized
Started Jetstream aggregator consumer: ws://localhost:6008/subscribe?...
- Indexing: social.coves.aggregator.service (service declarations)
- Indexing: social.coves.aggregator.authorization (authorization records)
Aggregator XRPC endpoints registered (query endpoints public)

Architecture:
- Aggregator service depends on: aggregator repo, community service
- Post service depends on: aggregator service (for auth checks)
- Jetstream consumer runs independently, indexes to DB via repository
- XRPC handlers call service layer methods

Phase 1 complete:
✅ Aggregators can authenticate (via JWT)
✅ Aggregators can post to authorized communities
✅ Rate limiting enforced (10 posts/hour per community)
✅ Query endpoints available for discovery

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

Changed files
+37 -2
cmd
server
+37 -2
cmd/server/main.go
···
"Coves/internal/atproto/auth"
"Coves/internal/atproto/identity"
"Coves/internal/atproto/jetstream"
+
"Coves/internal/core/aggregators"
"Coves/internal/core/communities"
"Coves/internal/core/communityFeeds"
"Coves/internal/core/posts"
···
log.Println("Started JWKS cache cleanup background job (runs hourly)")
-
// Initialize post service
+
// Initialize aggregator service
+
aggregatorRepo := postgresRepo.NewAggregatorRepository(db)
+
aggregatorService := aggregators.NewAggregatorService(aggregatorRepo, communityService)
+
log.Println("✅ Aggregator service initialized")
+
+
// Initialize post service (with aggregator support)
postRepo := postgresRepo.NewPostRepository(db)
-
postService := posts.NewPostService(postRepo, communityService, defaultPDS)
+
postService := posts.NewPostService(postRepo, communityService, aggregatorService, defaultPDS)
// Initialize feed service
feedRepo := postgresRepo.NewCommunityFeedRepository(db)
···
log.Println(" - Indexing: social.coves.post.record CREATE operations")
log.Println(" - UPDATE/DELETE indexing deferred until those features are implemented")
+
// Start Jetstream consumer for aggregators
+
// This consumer indexes aggregator service declarations and authorization records
+
// Following Bluesky's pattern for feed generators and labelers
+
// NOTE: Uses the same Jetstream as communities, just filtering different collections
+
aggregatorJetstreamURL := communityJetstreamURL
+
// Override if specific URL needed for testing
+
if envURL := os.Getenv("AGGREGATOR_JETSTREAM_URL"); envURL != "" {
+
aggregatorJetstreamURL = envURL
+
} else if aggregatorJetstreamURL == "" {
+
// Fallback if community URL also not set
+
aggregatorJetstreamURL = "ws://localhost:6008/subscribe?wantedCollections=social.coves.aggregator.service&wantedCollections=social.coves.aggregator.authorization"
+
}
+
+
aggregatorEventConsumer := jetstream.NewAggregatorEventConsumer(aggregatorRepo)
+
aggregatorJetstreamConnector := jetstream.NewAggregatorJetstreamConnector(aggregatorEventConsumer, aggregatorJetstreamURL)
+
+
go func() {
+
if startErr := aggregatorJetstreamConnector.Start(ctx); startErr != nil {
+
log.Printf("Aggregator Jetstream consumer stopped: %v", startErr)
+
}
+
}()
+
+
log.Printf("Started Jetstream aggregator consumer: %s", aggregatorJetstreamURL)
+
log.Println(" - Indexing: social.coves.aggregator.service (service declarations)")
+
log.Println(" - Indexing: social.coves.aggregator.authorization (authorization records)")
+
// Register XRPC routes
routes.RegisterUserRoutes(r, userService)
routes.RegisterCommunityRoutes(r, communityService, authMiddleware)
···
routes.RegisterCommunityFeedRoutes(r, feedService)
log.Println("Feed XRPC endpoints registered (public, no auth required)")
+
+
routes.RegisterAggregatorRoutes(r, aggregatorService)
+
log.Println("Aggregator XRPC endpoints registered (query endpoints public)")
r.Get("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)