feat/user-api-stubs
Summary
Replaced all stub implementations in UserApiService, EventApiService, and related screens with real Supabase operations. Also fixed a critical navigation bug in the public profile screen.
Changes
user_api_service.dart — Follow / Unfollow / Block / Report wired to Supabase
All social graph operations were previously no-ops returning hardcoded true.
Follow / Unfollow:
followUser—UPSERT user_follows (follower_id, following_id)withonConflict: 'follower_id,following_id'unfollowUser—DELETE user_follows WHERE follower_id AND following_idisFollowingUser—SELECT follower_id LIMIT 1, returnsrows.isNotEmptygetUserFollowers—user_follows JOIN profiles!user_follows_follower_id_fkeypaginatedgetUserFollowing—user_follows JOIN profiles!user_follows_following_id_fkeypaginated
Block / Unblock:
blockUser—UPSERT user_blocks (blocker_id, blocked_id)withonConflict: 'blocker_id,blocked_id'unblockUser—DELETE user_blocks WHERE blocker_id AND blocked_idgetBlockedUsers—user_blocks JOIN profiles!user_blocks_blocked_id_fkeypaginated
Report:
reportUser—INSERT user_reports (reporter_id, reported_id, reason, description)
Required DB tables (migrations 20260318000005, 20260318000006):
user_blocks (blocker_id, blocked_id)withPRIMARY KEY,no_self_blockCHECK, RLS (self-manage only)user_reports (reporter_id, reported_id, reason, description, status)withno_self_reportCHECK, valid_status CHECK, RLS (insert/read own)
event_api_service.dart — updateEvent and deleteEvent
updateEvent— realUPDATE events SET ... WHERE id AND organizer_id = currentUser, returns updated Event. Owner-only via Supabase.eq('organizer_id', userId).deleteEvent— soft-delete:UPDATE events SET status='cancelled'(owner-only). Preserves data, RSVP history, and attendee records.
event_details_screen.dart — Organizer follow wired to UserApiService
Previously _toggleFollowOrganizer only toggled local state with no API call.
- Added
UserApiService _userApiServicefield initStatecalls_loadFollowState()→isFollowingUser(organizerId)to initialize_isFollowingOrganizer_toggleFollowOrganizernow async: optimistic local update →followUser/unfollowUserAPI call → revert on failure with SnackBar feedback
public_profile_screen.dart — Chat button navigates to real DM
Bug: context.push('/social/chat/${user.id}') passed a profile UUID as the chat route param. The chat screen calls fetchChat(chatId) which found no chats row with that UUID, causing an empty/error state.
Fix: Replace direct push with:
onTap: () async {
final chat = await MessagingApiService().createDirectChat(user.id);
if (chat != null && context.mounted) {
context.push('/social/chat/${chat.id}');
}
},createDirectChat finds the existing DM room or creates a new one, returning a valid Chat with a real chats.id. The context.mounted guard prevents navigation on a disposed widget.