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.
Implement XRPC-style HTTP handlers for comment write operations:
- CreateCommentHandler: POST /xrpc/social.coves.community.comment.create
- UpdateCommentHandler: POST /xrpc/social.coves.community.comment.update
- DeleteCommentHandler: POST /xrpc/social.coves.community.comment.delete
Features:
- Request body size limit (100KB) for DoS prevention
- OAuth session extraction from middleware context
- Proper error mapping to lexicon-defined error types
- Labels validation with explicit error handling
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implement comment write operations following write-forward architecture:
- CreateComment: Write new comments/replies to user's PDS
- UpdateComment: Modify existing comment content (preserves reply refs)
- DeleteComment: Remove comments via PDS deleteRecord
Key features:
- Proper grapheme counting with unicode/uniseg library
- Collection validation to prevent cross-collection attacks
- Ownership verification before update/delete
- OAuth session-based PDS client creation
- PDSClientFactory for testability with password auth
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Define AT Protocol lexicon schemas for comment write operations:
- social.coves.community.comment.create: Create new comments/replies
- social.coves.community.comment.update: Update existing comment content
- social.coves.community.comment.delete: Delete comments by URI
All procedures require OAuth authentication and follow atProto conventions
with proper error definitions (ContentTooLong, ContentEmpty, NotAuthorized, etc.)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add stale vote cleanup in Jetstream consumer: when indexing a vote,
detect and soft-delete any existing active vote with a different URI
for the same user/subject (handles missed delete events)
- Change indexVoteAndUpdateCounts to return (bool, error) to indicate
if vote was newly inserted vs already existed
- Remove noisy "Vote already indexed" log for idempotent cases
- Only log "✓ Indexed vote" when vote is actually new
- Update CLAUDE.md with PR reviewer instructions
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add vote caching to solve eventual consistency issues when displaying
user vote state in community feeds and timeline. The cache is populated
from the user's PDS (source of truth) on first authenticated request,
avoiding stale data from the AppView database.
Changes:
- Add VoteCache with TTL-based expiration and incremental updates
- Integrate cache into feed and timeline handlers for viewer vote state
- Add EnsureCachePopulated and GetViewerVotesForSubjects to vote service
- Add reindex-votes CLI tool for rebuilding vote counts from PDS
- Update CLAUDE.md to PR reviewer persona
- Fix E2E tests to properly simulate Jetstream events
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This PR addresses two issues from code review:
1. **Fragile string matching for error detection** - The vote service was
using `strings.Contains(err.Error(), "401")` which is brittle. Now uses
typed errors (`pds.ErrUnauthorized`, `pds.ErrForbidden`) with `errors.Is()`.
2. **Auth error handling regression** - The PDS client refactor removed
401/403 mapping for write operations, causing expired sessions to return
500 errors instead of prompting re-authentication. This is now fixed.
Changes:
- Add internal/atproto/pds package with Client interface abstraction
- Add typed errors: ErrUnauthorized, ErrForbidden, ErrNotFound, ErrBadRequest
- Add wrapAPIError() that inspects atclient.APIError status codes
- Add IsAuthError() convenience helper
- Update vote service to use pds.IsAuthError() for all PDS operations
- Add comprehensive unit tests for error handling
- Add PasswordAuthPDSClientFactory for E2E test compatibility
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add //go:build dev tags to dev_resolver.go and dev_auth_resolver.go
- Create dev_stubs.go with production stubs (//go:build !dev)
- Fix mobile OAuth flow: localhost→127.0.0.1 redirect for cookie consistency
- Fix handle verification via local PDS in callback handler
- Use config.PublicURL for OAuth callback instead of hardcoded localhost
- Add build-dev Makefile target for dev builds
- Update dev-run.sh to use -tags dev
- Create .env.dev.example template (safe to commit)
- Document dev mode configuration in .env.dev
Dev mode code is now physically excluded from production builds.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add `deleted_at IS NULL` filter to GetByURI() in vote repository
to properly exclude soft-deleted votes (matching GetByVoterAndSubject behavior)
- Fix TestVoteE2E_ToggleDifferentDirection to simulate correct event sequence:
When changing vote direction, the service DELETEs old vote and CREATEs new one
with a new rkey (not UPDATE). Test now simulates DELETE + CREATE events.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Remove SubjectValidator and SubjectNotFound error - votes on non-existent
or deleted subjects are now allowed. This aligns with atproto's design:
- User's PDS accepts the vote regardless (they own their repo)
- Jetstream emits the event regardless
- AppView consumer correctly handles orphaned votes by only updating
counts for non-deleted subjects
Benefits:
- Reduced latency (no extra DB queries per vote)
- No race conditions (subject could be deleted between validation and PDS write)
- No eventual consistency issues (subject might not be indexed yet)
- Simpler code and fewer failure modes
Changes:
- Remove SubjectValidator interface and CompositeSubjectValidator
- Remove ErrSubjectNotFound from errors and lexicon
- Update NewService signature to remove validator parameter
- Update tests to remove SubjectNotFound test cases
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
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>