Clone this repository
For self-hosted knots, clone URLs may differ based on your setup.
Download tar.gz
Replace plain Bearer token authentication with proper DPoP-authenticated
OAuth sessions for subscribe, unsubscribe, block, and unblock operations.
The issue was that atProto OAuth tokens are DPoP-bound and require both
an access token AND a DPoP proof header. The community service was using
plain Bearer authentication which caused "Malformed token" errors.
Changes:
- Update Service interface to use *oauth.ClientSessionData
- Add PDSClientFactory pattern for testability
- Update handlers to get OAuth session from middleware
- Update unit tests to inject OAuth session into context
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add user comment history endpoint for profile pages with:
- Lexicon definition following AT Protocol conventions
- Handler with proper error handling (400/404/500 distinction)
- Cursor-based pagination with composite key (createdAt|uri)
- Optional community filtering
- Viewer vote state population for authenticated users
- Comprehensive handler tests (15 tests)
- Comprehensive service tests (13 tests)
Key fixes from PR review:
- resolutionFailedError now returns 500 (not 400)
- Added warning log for missing votes table
- Improved SQL parameter documentation for cursor filters
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add aggregated user statistics to the social.coves.actor.getProfile response:
- postCount, commentCount, communityCount, membershipCount, reputation
- Efficient single-query with scalar subqueries
- Flat response structure matching lexicon specification
PR review fixes:
- Log database errors in ResolveHandleToDID before fallback (silent-failure-hunter)
- Marshal JSON to bytes before writing to prevent partial responses
- Wrap GetByDID errors with context for debugging
- Document intentional reputation counting for banned users
Tests: comprehensive coverage for all stat types including soft-delete
filtering, subscription counting, and non-existent DID handling.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The Jetstream identity event consumer was intentionally changed to only
UPDATE existing users, not create new ones. This prevents indexing
millions of Bluesky users who never interact with Coves. Users are now
indexed during:
- OAuth login
- Signup (via RegisterAccount.IndexUser())
Test fixes:
- integration/jetstream_consumer_test.go: Pre-create users before
testing identity event handling; renamed tests to reflect behavior
- integration/community_e2e_test.go: Add test data cleanup to prevent
cross-test pollution affecting alphabetical sort test
- integration/user_test.go: Add comprehensive test data cleanup
(subscriptions, posts, communities) in setupTestDB
- e2e/error_recovery_test.go: Pre-create users in all identity event
tests (reconnection, malformed events, PDS unavailability, etc.)
- e2e/user_signup_test.go: Query AppView API instead of test database
to verify user creation; removed unused Jetstream consumer setup
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add test-all target that runs all tests with live infrastructure
- Check dev stack, AppView, and test database before running tests
- Add LOG_ENABLED=false support to silence app logs during tests
- Add TestMain to integration, e2e, unit, and tests packages
- Fail fast on first test failure for quick feedback
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add complete implementation for retrieving a user's posts by DID or handle:
- New XRPC endpoint: GET /xrpc/social.coves.actor.getPosts
- Handle resolution with local DB fallback (faster for indexed users)
- Filters: posts_with_replies, posts_no_replies, posts_with_media
- Community-scoped filtering
- Cursor-based pagination with proper duplicate prevention
- Viewer vote state population (when authenticated)
- Bluesky post embed resolution
Key improvements:
- ResolveHandleToDID now checks local DB first before external DNS/HTTPS
- Proper error distinction: actorNotFoundError vs resolutionFailedError
- Infrastructure failures (DB down, DNS errors) return 500, not 404
Includes comprehensive E2E tests against live PDS infrastructure.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add UserIndexer interface and IndexUser method to index users after
successful OAuth authentication
- Change Jetstream consumer to only UPDATE existing users, not create new
ones (prevents indexing millions of unrelated Bluesky users)
- Add sentinel errors ErrUserNotFound and ErrHandleAlreadyTaken for
proper error handling instead of string matching
- Fix silent failure: Jetstream now propagates database errors instead
of silently dropping events during outages
- Remove redundant DID lookup in OAuth callback by reusing verifiedIdent
- Remove bsky.social fallback - not all users are on Bluesky
- Add compile-time interface satisfaction check for UserIndexer
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The hot_rank cursor was using NOW() for comparison, but time passes between
page requests. This caused posts to drift across cursor boundaries, resulting
in duplicates appearing on subsequent pages.
Fix: Store cursor creation timestamp in the cursor and use it for hot_rank
computation in subsequent queries, ensuring stable comparisons.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Aggregator thumbnail uploads were failing for larger images. Increased
the blob size limit to 6MB to match Bluesky's standard blob limits.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Cron runs in a separate environment that doesn't inherit the container's
environment variables. This caused the COVES_API_KEY to be unavailable
when the cron job executed, resulting in failed runs.
Fix:
- Export COVES_* and PATH to /etc/environment at container startup
- Source /etc/environment in crontab before running the aggregator
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Creates a test aggregator account on the local PDS for development.
Inserts directly into users/aggregators tables to simulate the full
aggregator registration flow locally.
Usage: go run scripts/setup_dev_aggregator.go
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>