···
import '../config/environment_config.dart';
import '../models/coves_session.dart';
import 'api_exceptions.dart';
···
/// 1. Unseals the token to get the actual access/refresh tokens
/// 2. Uses stored DPoP keys to sign requests
/// 3. Writes to the user's PDS on their behalf
-
/// TODO: Backend vote endpoints need to be implemented:
-
/// - POST /xrpc/social.coves.feed.vote.create
-
/// - POST /xrpc/social.coves.feed.vote.delete
-
/// - GET /xrpc/social.coves.feed.vote.list (or included in feed response)
Future<CovesSession?> Function()? sessionGetter,
···
/// Collection name for vote records
static const String voteCollection = 'social.coves.feed.vote';
-
/// Get all votes for the current user
-
/// TODO: This needs a backend endpoint to list user's votes.
-
/// For now, returns empty map - votes will be fetched with feed data.
-
/// - `Map<String, VoteInfo>` where key is the post URI
-
/// - Empty map if not authenticated or no votes found
-
Future<Map<String, VoteInfo>> getUserVotes() async {
-
final userDid = _didGetter?.call();
-
if (userDid == null || userDid.isEmpty) {
-
final session = await _sessionGetter?.call();
-
// TODO: Implement backend endpoint for listing user votes
-
// For now, vote state should come from feed responses
-
'⚠️ getUserVotes: Backend endpoint not yet implemented. '
-
'Vote state should come from feed responses.',
-
} on Exception catch (e) {
-
debugPrint('⚠️ Failed to load user votes: $e');
/// Create or toggle vote
-
/// Sends vote request to the Coves backend, which proxies to the user's PDS.
/// - [postUri]: AT-URI of the post
/// - [postCid]: Content ID of the post (for strong reference)
/// - [direction]: Vote direction - "up" for like/upvote, "down" for downvote
-
/// - [existingVoteRkey]: Optional rkey from cached state
-
/// - [existingVoteDirection]: Optional direction from cached state
-
/// - VoteResponse with uri/cid/rkey if created
-
/// - VoteResponse with deleted=true if toggled off
/// - ApiException for API errors
···
-
String? existingVoteRkey,
-
String? existingVoteDirection,
final userDid = _didGetter?.call();
···
debugPrint(' Direction: $direction');
-
// Determine if this is a toggle (delete) or create
-
existingVoteRkey != null && existingVoteDirection == direction;
-
// Delete existing vote
-
return _deleteVote(session: session, rkey: existingVoteRkey);
-
// If switching direction, delete old vote first
-
if (existingVoteRkey != null && existingVoteDirection != null) {
-
debugPrint(' Switching vote direction - deleting old vote first');
-
await _deleteVote(session: session, rkey: existingVoteRkey);
-
// Create new vote via backend
// Note: Authorization header is added by the interceptor
final response = await _dio.post<Map<String, dynamic>>(
'/xrpc/social.coves.feed.vote.create',
···
final uri = data['uri'] as String?;
final cid = data['cid'] as String?;
-
if (uri == null || cid == null) {
-
throw ApiException('Invalid response from server - missing uri or cid');
-
// Extract rkey from URI
-
final rkey = uri.split('/').last;
debugPrint('✅ Vote created: $uri');
···
throw ApiException('Failed to create vote: $e');
-
/// Delete vote via backend
-
Future<VoteResponse> _deleteVote({
-
required CovesSession session,
-
// Note: Authorization header is added by the interceptor
-
'/xrpc/social.coves.feed.vote.delete',
-
debugPrint('✅ Vote deleted');
-
return const VoteResponse(deleted: true);
-
} on DioException catch (e) {
-
debugPrint('❌ Delete vote failed: ${e.message}');
-
'Failed to delete vote: ${e.message}',
-
statusCode: e.response?.statusCode,
···
/// Whether the vote was deleted (toggled off)
-
/// Represents a vote that already exists on the PDS.
-
const ExistingVote({required this.direction, required this.rkey});
-
/// Vote direction ("up" or "down")
-
final String direction;
-
/// Record key for deletion
-
/// Information about a user's vote on a post, returned from getUserVotes().
-
required this.direction,
-
/// Vote direction ("up" or "down")
-
final String direction;
-
/// AT-URI of the vote record
-
/// Record key (rkey) - last segment of URI