170 lines
5.2 KiB
Markdown
170 lines
5.2 KiB
Markdown
# 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<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`:
|
|
|
|
```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
|