# Social Graph & Privacy Implementation Status ## ✅ Completed Backend Features ### 1. Database Schema - **Circle Members Table**: `public.circle_members` created with user_id/member_id pairs - **SQL Function**: `is_in_circle(owner_id, user_id)` for efficient membership checks - **Migration**: `20260204000002_circle_privacy.up.sql` ready to apply ### 2. Repository Methods (Go Backend) #### Followers & Following - `GetFollowers(ctx, userID, limit, offset)` - Returns paginated list with harmony scores/tiers - `GetFollowing(ctx, userID, limit, offset)` - Returns paginated list with harmony scores/tiers #### Circle Management - `AddToCircle(ctx, userID, memberID)` - Add user to circle (validates following first) - `RemoveFromCircle(ctx, userID, memberID)` - Remove from circle - `GetCircleMembers(ctx, userID)` - List all circle members - `IsInCircle(ctx, ownerID, userID)` - Check membership #### Data Export - `ExportUserData(ctx, userID)` - Complete export (profile, posts, following list) ### 3. API Endpoints All routes registered in `cmd/api/main.go`: ``` GET /api/v1/users/:id/followers - Get user's followers list GET /ap/v1/users/:id/following - Get list of users they follow POST /api/v1/users/circle/:id - Add user to your circle DELETE /api/v1/users/circle/:id - Remove user from circle GET /api/v1/users/circle/members - Get your circle members GET /api/v1/users/me/export - Export your complete data ``` ## ⚠️ Remaining Implementation ### Circle Privacy in Feed Queries Add this WHERE clause to `post_repository.go` in these methods: - `GetFeed()` (line ~156) - `GetPostsByAuthor()` (line ~243) - `GetPostByID()` (line ~310) **Code to add** (after the blocking check): ```go AND ( p.visibility != 'circle' -- Non-circle posts visible to all OR p.author_id = CASE WHEN $4::text != '' THEN $4::text::uuid ELSE NULL END -- Author sees own circle posts OR public.is_in_circle(p.author_id, CASE WHENylt:text != '' THEN $4::text::uuid ELSE NULL END) -- Circle members see circle posts ) ``` ### Frontend Flutter Implementation #### 1. Update Following Screen (`lib/screens/profile/following_screen.dart`) Replace `_generateMockData()` with real API call: ```dart Future _loadFollowing() async { setState(() { _isLoading = true; _error = null; }); try { final api = ref.read(apiServiceProvider); final currentUser = ref.read(currentUserProvider); final data = await api.callGoApi( '/users/${currentUser?.id}/following', queryParams: {'limit': '20', 'offset': '${_followedUsers.length}'}, ); final List users = (data['following'] as List) .map( (json) => FollowedUser.fromJson(json)) .toList(); setState(() { _followedUsers = users; }); } catch (e) { setState(() { _error = e.toString(); }); } finally { setState(() { _isLoading = false; }); } } ``` #### 2. Create Followers Screen Create `lib/screens/profile/followers_screen.dart` - similar structure to `FollowingScreen` but calling `/users/:id/followers`. #### 3. Add Circle Management Screen Create `lib/screens/settings/circle_management_screen.dart`: - List current circle members (GET `/users/circle/members`) - Show "Add to Circle" button on following list - Handle add (POST `/users/circle/:id`) and remove (DELETE `/users/circle/:id`) #### 4. Data Export Button In `lib/screens/settings/profile_settings_screen.dart`: ```dart ListTile( leading: Icon(Icons.download), title: Text('Export My Data'), subtitle: Text('Download your profile, posts, and connections'), onTap: () async { final api = ref.read(apiServiceProvider); final data = await api.callGoApi('/users/me/export'); // Save to file final file = File('${documentsDir}/sojorn_export.json'); await file.writeAsString(jsonEncode(data)); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Data exported successfully!')), ); }, ) ``` #### 5. Circle Visibility in Compose In `lib/screens/compose/compose_screen.dart`, ensure "Circle" option in visibility dropdown: - Options: 'public', 'friends', 'circle' - When "Circle" is selected, post won't appear in feed for non-circle members ## Migration Instructions 1. **Apply Database Migration**: ```bash cd go-backend migrate -path internal/database/migrations -database "your_db_url" up ``` 2. **Restart Go Backend** to load new routes 3. **Test Endpoints**: ```bash # Get followers curl -H "Authorization: Bearer $TOKEN" \ "http://localhost:8080/api/v1/users/USER_ID/followers" # Export data curl -H "Authorization: Bearer $TOKEN" \ "http://localhost:8080/api/v1/users/me/export" > export.json ``` 4. **Update Flutter App**: - Implement the frontend screens listed above - Test circle visibility by creating circle-only posts - Verify data export downloads correctly ## Security Notes - Circle membership is verified via `is_in_circle()` SQL function (performant, indexed) - Blocked users cannot see any posts (via `has_block_between()`) - Data export only returns user's own data (enforced by JWT user_id) - All endpoints require authentication via JWT middleware