5.2 KiB
5.2 KiB
Social Graph & Privacy Implementation Status
✅ Completed Backend Features
1. Database Schema
- Circle Members Table:
public.circle_memberscreated with user_id/member_id pairs - SQL Function:
is_in_circle(owner_id, user_id)for efficient membership checks - Migration:
20260204000002_circle_privacy.up.sqlready to apply
2. Repository Methods (Go Backend)
Followers & Following
GetFollowers(ctx, userID, limit, offset)- Returns paginated list with harmony scores/tiersGetFollowing(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 circleGetCircleMembers(ctx, userID)- List all circle membersIsInCircle(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
-
Apply Database Migration:
cd go-backend migrate -path internal/database/migrations -database "your_db_url" up -
Restart Go Backend to load new routes
-
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 -
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