sojorn/sojorn_docs/SOCIAL_GRAPH_IMPLEMENTATION.md

5.2 KiB

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):

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:

Future<void> _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<FollowedUser> 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:

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:

    cd go-backend
    migrate -path internal/database/migrations -database "your_db_url" up
    
  2. Restart Go Backend to load new routes

  3. Test Endpoints:

    # 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