code
Clone this repository
https://tangled.org/bretton.dev/coves
git@knot.bretton.dev:bretton.dev/coves
For self-hosted knots, clone URLs may differ based on your setup.
P1 fixes:
- Use errors.Is() in handler to match wrapped errors (ErrNotAuthorized
was returning 500 instead of 403 when wrapped)
P2 fixes:
- Add SubjectValidator interface to check post/comment existence
- Service now validates subject exists before creating vote
- Returns ErrSubjectNotFound per lexicon if subject doesn't exist
- Prevents dangling votes on non-existent content
Also:
- Add CompositeSubjectValidator for checking both posts and comments
- Wire up subject validation in main.go with post/comment repos
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Wire up vote service and routes in main server:
- Initialize VoteService with OAuth client for PDS authentication
- Register vote XRPC routes with auth middleware
Also adds E2E test helpers:
- AddSessionWithPDS: Store session with specific PDS URL
- AddUserWithPDSToken: Register user with real PDS access token
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Comprehensive E2E tests covering:
- TestVoteE2E_CreateUpvote: Full create flow with PDS verification
- TestVoteE2E_ToggleSameDirection: Toggle off behavior
- TestVoteE2E_ToggleDifferentDirection: Vote direction change
- TestVoteE2E_DeleteVote: Explicit delete via XRPC
- TestVoteE2E_JetstreamIndexing: Real Jetstream firehose consumption
Tests verify:
- Vote records written to PDS correctly
- Jetstream consumer indexes votes
- Post vote counts updated
- Empty object response for delete per lexicon
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Register vote XRPC endpoints on the router:
- POST /xrpc/social.coves.feed.vote.create (authenticated)
- POST /xrpc/social.coves.feed.vote.delete (authenticated)
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add HTTP handlers for vote XRPC endpoints:
- HandleCreateVote: POST /xrpc/social.coves.feed.vote.create
- HandleDeleteVote: POST /xrpc/social.coves.feed.vote.delete
Error handling matches lexicon exactly:
- VoteNotFound, SubjectNotFound, InvalidSubject, NotAuthorized
- Delete returns empty object {} per lexicon spec
Includes comprehensive unit tests for all handlers.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implements vote service that queries PDS directly for existing votes,
avoiding eventual consistency issues with the AppView database.
Key features:
- CreateVote with toggle behavior (same direction = delete)
- DeleteVote for explicit vote removal
- getVoteFromPDS: Paginates through all vote records to handle >100 votes
- Proper auth error handling (401/403 -> ErrNotAuthorized)
Architecture:
- Queries PDS via com.atproto.repo.listRecords (source of truth)
- Writes via com.atproto.repo.createRecord/deleteRecord
- AppView indexes from Jetstream for aggregate counts only
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add XRPC procedure lexicons for voting on posts/comments:
- social.coves.feed.vote.create: Create/toggle votes with up/down direction
- social.coves.feed.vote.delete: Remove existing votes
Follows atproto lexicon best practices:
- Uses knownValues for direction (not closed enum)
- References com.atproto.repo.strongRef for subject
- UpperCamelCase error names per spec
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Instead of redirecting directly to the custom scheme (social.coves:/),
serve an intermediate HTML page that:
- Shows "Login Complete" with Coves branding
- Displays the user's handle
- Redirects to the app via JavaScript + meta refresh
- Attempts to close the browser tab
- Shows friendly fallback message if app doesn't open
This prevents users from seeing stale PDS error pages when the
Custom Tab doesn't close immediately after the OAuth redirect.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Per atproto OAuth spec, native mobile apps can use custom URL schemes
where the scheme matches the client_id hostname in reverse-domain order.
For coves.social, the allowed scheme is "social.coves".
Supported redirect URIs:
- social.coves:/callback (custom scheme per atproto spec)
- social.coves://callback
- social.coves:/oauth/callback
- social.coves://oauth/callback
- https://coves.social/app/oauth/callback (Universal Link)
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- assetlinks.json for Android App Links
- apple-app-site-association for iOS Universal Links
Note: iOS file needs TEAM_ID replaced with actual Apple Developer Team ID
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
When Universal Links don't intercept the redirect to /app/oauth/callback,
return a clear error instead of trying to process it as an OAuth callback.
馃 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>