core/docs/follower.md

220 lines
6.2 KiB
Markdown
Raw Permalink Normal View History

2024-11-20 20:59:11 +00:00
# Following System
## Overview
System supports following different entity types:
- Authors
- Topics
- Communities
- Shouts (Posts)
## GraphQL API
### Mutations
#### follow
Follow an entity (author/topic/community/shout).
**Parameters:**
- `what: String!` - Entity type (`AUTHOR`, `TOPIC`, `COMMUNITY`, `SHOUT`)
- `slug: String` - Entity slug
- `entity_id: Int` - Optional entity ID
**Returns:**
```typescript
{
authors?: Author[] // For AUTHOR type
topics?: Topic[] // For TOPIC type
communities?: Community[] // For COMMUNITY type
shouts?: Shout[] // For SHOUT type
error?: String // Error message if any
}
```
#### unfollow
Unfollow an entity.
**Parameters:** Same as `follow`
**Returns:** Same as `follow`
2025-05-31 14:18:31 +00:00
**Important:** Always returns current following list even if the subscription was not found, ensuring UI consistency.
2024-11-20 20:59:11 +00:00
### Queries
#### get_shout_followers
2025-06-30 18:25:26 +00:00
Get list of authors who reacted to a shout.
2024-11-20 20:59:11 +00:00
**Parameters:**
- `slug: String` - Shout slug
- `shout_id: Int` - Optional shout ID
**Returns:**
```typescript
Author[] // List of authors who reacted
```
## Caching System
### Supported Entity Types
- Authors: `cache_author`, `get_cached_follower_authors`
- Topics: `cache_topic`, `get_cached_follower_topics`
- Communities: No cache
- Shouts: No cache
### Cache Flow
1. On follow/unfollow:
- Update entity in cache
2025-05-31 14:18:31 +00:00
- **Invalidate user's following list cache** (NEW)
2024-11-20 20:59:11 +00:00
- Update follower's following list
2. Cache is updated before notifications
2025-05-31 14:18:31 +00:00
### Cache Invalidation (NEW)
Following cache keys are invalidated after operations:
- `author:follows-topics:{user_id}` - After topic follow/unfollow
- `author:follows-authors:{user_id}` - After author follow/unfollow
2024-11-20 20:59:11 +00:00
2025-05-31 14:18:31 +00:00
This ensures fresh data is fetched from database on next request.
2024-11-20 20:59:11 +00:00
## Error Handling
2025-05-31 14:18:31 +00:00
### Enhanced Error Handling (UPDATED)
2024-11-20 20:59:11 +00:00
- Unauthorized access check
- Entity existence validation
- Duplicate follow prevention
2025-05-31 14:18:31 +00:00
- **Graceful handling of "following not found" errors**
- **Always returns current following list, even on errors**
2024-11-20 20:59:11 +00:00
- Full error logging
- Transaction safety with `local_session()`
2025-05-31 14:18:31 +00:00
### Error Response Format
```typescript
{
error?: "following was not found" | "invalid unfollow type" | "access denied",
topics?: Topic[], // Always present for topic operations
authors?: Author[], // Always present for author operations
// ... other entity types
}
```
## Recent Fixes (NEW)
2025-05-31 14:21:14 +00:00
### Issue 1: Stale UI State on Unfollow Errors
2025-05-31 14:18:31 +00:00
**Problem:** When unfollow operation failed with "following was not found", the client didn't update its state because it only processed successful responses.
**Root Cause:**
1. `unfollow` mutation returned error with empty follows list `[]`
2. Client logic: `if (result && !result.error)` prevented state updates on errors
3. User remained "subscribed" in UI despite no actual subscription in database
**Solution:**
1. **Always fetch current following list** from cache/database
2. **Return actual following state** even when subscription not found
3. **Add cache invalidation** after successful operations
4. **Enhanced logging** for debugging
2025-05-31 14:21:14 +00:00
### Issue 2: Inconsistent Behavior in Follow Operations (NEW)
**Problem:** The `follow` function had similar issues to `unfollow`:
- Could return `None` instead of actual following list in error scenarios
- Cache was not invalidated when trying to follow already-followed entities
- Inconsistent error handling between follow/unfollow operations
**Root Cause:**
1. `follow` mutation could return `{topics: null}` when `get_cached_follows_method` was not available
2. When user was already following an entity, cache invalidation was skipped
3. Error responses didn't include current following state
**Solution:**
1. **Always return actual following list** from cache/database
2. **Invalidate cache on every operation** (both new and existing subscriptions)
3. **Add "already following" error** while still returning current state
4. **Unified error handling** consistent with unfollow
2025-05-31 14:18:31 +00:00
### Code Changes
```python
2025-05-31 14:21:14 +00:00
# UNFOLLOW - Before (BROKEN)
2025-05-31 14:18:31 +00:00
if sub:
# ... process unfollow
else:
return {"error": "following was not found", f"{entity_type}s": follows} # follows was []
# UNFOLLOW - After (FIXED)
2025-05-31 14:18:31 +00:00
if sub:
# ... process unfollow
# Invalidate cache
await redis.execute("DEL", f"author:follows-{entity_type}s:{follower_id}")
else:
error = "following was not found"
# Always get current state
existing_follows = await get_cached_follows_method(follower_id)
return {f"{entity_type}s": existing_follows, "error": error}
2025-05-31 14:21:14 +00:00
# FOLLOW - Before (BROKEN)
if existing_sub:
logger.info(f"User already following...")
# Cache not invalidated, could return stale data
else:
# ... create subscription
# Cache invalidated only here
follows = None # Could be None!
# ... complex logic to build follows list
return {f"{entity_type}s": follows} # follows could be None
# FOLLOW - After (FIXED)
if existing_sub:
error = "already following"
else:
# ... create subscription
# Always invalidate cache and get current state
2025-05-31 14:21:14 +00:00
await redis.execute("DEL", f"author:follows-{entity_type}s:{follower_id}")
existing_follows = await get_cached_follows_method(follower_id)
return {f"{entity_type}s": existing_follows, "error": error}
2025-05-31 14:18:31 +00:00
```
2025-05-31 14:21:14 +00:00
### Impact
**Before fixes:**
- UI could show incorrect subscription state
- Cache inconsistencies between follow/unfollow operations
- Client-side logic `if (result && !result.error)` failed on valid error states
**After fixes:**
-**UI always receives current subscription state**
-**Consistent cache invalidation** on all operations
-**Unified error handling** between follow/unfollow
-**Client can safely update UI** even on error responses
2025-05-31 14:18:31 +00:00
## Notifications
- Sent when author is followed/unfollowed
- Contains:
- Follower info
- Author ID
- Action type ("follow"/"unfollow")
2024-11-20 20:59:11 +00:00
## Database Schema
### Follower Tables
- `AuthorFollower`
- `TopicFollower`
- `CommunityFollower`
- `ShoutReactionsFollower`
Each table contains:
- `follower` - ID of following user
2025-05-31 14:18:31 +00:00
- `{entity_type}` - ID of followed entity
## Testing
Run the test script to verify fixes:
```bash
python test_unfollow_fix.py
```
### Test Coverage
- ✅ Unfollow existing subscription
- ✅ Unfollow non-existent subscription
2025-05-31 14:18:31 +00:00
- ✅ Cache invalidation
- ✅ Proper error handling
- ✅ UI state consistency