Successfully implemented three new backend API endpoints for progress management in the FastAPI backend. All endpoints are authenticated, validated, and store updates in Supabase.
Purpose: Fetch complete user progress and XP data in a single API call
Features:
- Returns comprehensive user data (statistics, topics, XP, recent logs)
- Security: Users can only access their own data (403 Forbidden for others)
- Perfect for dashboard/profile pages
- Single call efficiency (no multiple requests needed)
Response includes:
- Statistics: total topics, completed, in-progress, average score, completion rate
- All topic progress records with scores and attempts
- Total XP and activity count
- Last 10 XP logs for recent activity
Example:
curl -X GET "http://localhost:8000/progress/{user_id}" \
-H "Authorization: Bearer YOUR_TOKEN"Purpose: Manually update XP after quiz completion or other activities
Features:
- Award XP for any activity (quizzes, streaks, achievements)
- Input validation: 1-1000 points, required reason, optional metadata
- Returns created XP log and updated total XP
- Stores in Supabase xp_logs table
Common use cases:
- Daily streak bonuses
- Achievement unlocks
- Custom activity rewards
- Event-based XP
Example:
curl -X POST "http://localhost:8000/progress/update" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"points": 50,
"reason": "daily_streak",
"metadata": {"streak_days": 7}
}'Purpose: Reset topic progress to allow users to start over
Features:
- Deletes progress record for specific topic
- Preserves XP logs (earned XP not removed)
- Returns 404 if topic doesn't exist
- Allows fresh start on the topic
Use cases:
- User wants to re-learn a topic
- Reset statistics for better score
- Clear progress to retake quizzes
Example:
curl -X POST "http://localhost:8000/progress/reset" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"topic": "Neural Networks"}'class UpdateXPRequest(BaseModel):
points: int = Field(..., ge=1, le=1000)
reason: str = Field(..., min_length=1, max_length=100)
metadata: Optional[Dict] = Field(default={})
class ResetProgressRequest(BaseModel):
topic: str = Field(..., min_length=1, max_length=200)- ✅ JWT authentication required on all endpoints
- ✅ User-specific data access enforced
- ✅ 403 Forbidden when accessing other users' data
- ✅ Input validation with Pydantic
- ✅ Row Level Security in Supabase
All endpoints update Supabase in real-time:
| Endpoint | Database Action |
|---|---|
| GET /{user_id} | SELECT (read-only) |
| POST /update | INSERT into xp_logs |
| POST /reset | DELETE from progress |
Comprehensive error responses:
- 400: Bad Request (invalid input)
- 401: Unauthorized (missing/invalid token)
- 403: Forbidden (accessing other user's data)
- 404: Not Found (topic doesn't exist)
- 422: Validation Error (field constraints)
- 500: Internal Server Error (database issues)
File: backend/test_progress_endpoints.py
Coverage:
- ✅ All 3 new endpoints tested
- ✅ Security tests (403 Forbidden)
- ✅ Validation tests (422 errors)
- ✅ Not found tests (404 errors)
- ✅ Integration workflows
- ✅ 8 comprehensive test scenarios
Run tests:
cd backend
python3 test_progress_endpoints.pyComplete API reference with:
- Detailed endpoint descriptions
- Request/response examples
- Error handling guide
- Code examples (cURL, Python, JavaScript)
- Frontend integration tips
- Complete workflow examples
- Added new endpoints to API section
- Request/response examples
- Security notes
- Use case descriptions
- Implementation summary
- File changes overview
- Testing checklist
- Next steps guide
New Files (3):
backend/test_progress_endpoints.py- Comprehensive test suitedocs/progress-api-reference.md- Complete API documentationdocs/IMPLEMENTATION_PROGRESS_XP.md- Implementation summary
Modified Files (2):
backend/routes/progress.py- Added 3 new endpointsdocs/progress-tracking.md- Updated with new endpoints
Total: 1,715 insertions, 1 deletion
Dashboard Page:
// Single API call for complete dashboard
const response = await fetch(`/progress/${userId}`, {
headers: { 'Authorization': `Bearer ${token}` }
});
const data = await response.json();
// Display total XP
document.getElementById('xp').textContent = data.xp.total_xp;
// Display statistics
document.getElementById('completed').textContent =
data.statistics.completed_topics;
// Display topics
data.topics.forEach(topic => {
// Render topic card with progress
});Daily Streak Bonus:
// Award XP for 7-day streak
await fetch('/progress/update', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
points: 50,
reason: 'daily_streak',
metadata: { streak_days: 7 }
})
});Reset Topic:
// Allow user to restart a topic
await fetch('/progress/reset', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
topic: 'Neural Networks'
})
});| Endpoint | Method | Auth | Rate Limit | Purpose |
|---|---|---|---|---|
/health |
GET | ❌ | 10/min | Basic health check |
/health/detailed |
GET | ❌ | 10/min | Detailed health with dependencies |
/progress/{user_id} |
GET | ✅ | 10/min | Fetch all user data |
/progress/update |
POST | ✅ | 10/min | Award XP |
/progress/reset |
POST | ✅ | 10/min | Reset topic |
/progress/evaluate |
POST | ✅ | 10/min | Evaluate quiz (existing) |
/progress/stats |
GET | ✅ | 10/min | Get statistics (existing) |
/progress/topics |
GET | ✅ | 10/min | Get all topics (existing) |
/progress/topics/{topic} |
GET | ✅ | 10/min | Get topic details (existing) |
/progress/xp |
GET | ✅ | 10/min | Get total XP (existing) |
/progress/xp/logs |
GET | ✅ | 10/min | Get XP history (existing) |
/coach/feedback/{user_id} |
GET | ✅ | 5/min | Get AI coaching feedback |
/study/retry |
POST | ✅ | 5/min | Retry study session |
/quiz/* |
POST | ✅ | 5/min | Quiz generation endpoints |
points: Must be 1-1000 (enforced by Pydantic)reason: 1-100 characters, requiredmetadata: Optional, any valid JSON object
topic: 1-200 characters, required
-
Create Dashboard Component
- Call GET /progress/{user_id}
- Display total XP in header
- Show topic cards with progress bars
- Display recent XP activity feed
-
Implement XP Notifications
- Show toast/modal after XP gain
- Animate XP counter increase
- Celebrate milestones (100, 500, 1000 XP)
-
Add Topic Reset Button
- Confirm dialog before reset
- Call POST /progress/reset
- Refresh topic list after reset
-
Daily Streak Tracker
- Track login days
- Call POST /progress/update for streaks
- Display streak counter
Before deploying to production:
- All 3 endpoints implemented
- Pydantic validation working
- Authentication required
- Security checks (403 for other users)
- Database updates working
- Error handling comprehensive
- Test script created and passing
- API documentation complete
- Frontend integration tested
- Migration run in production Supabase
- End-to-end testing with real users
-
Commit 5973423: Progress tracking and XP gamification system
- Database schema (progress, xp_logs tables)
- ProgressTracker and XPTracker utilities
- Initial endpoints (stats, topics, xp)
-
Commit 6c9382c: Backend API endpoints for progress management (current)
- GET /progress/{user_id}
- POST /progress/update
- POST /progress/reset
- Test script and documentation
- API Reference:
docs/progress-api-reference.md - Progress Tracking:
docs/progress-tracking.md - Setup Guide:
docs/setup-progress-tables.md - Test Script:
backend/test_progress_endpoints.py - Implementation:
docs/IMPLEMENTATION_PROGRESS_XP.md
Purpose: Basic health check for monitoring and load balancers
Authentication: Not required
Rate Limit: 10 requests/minute
Response:
{
"status": "healthy",
"timestamp": "2025-11-22T10:30:00.000Z"
}Example:
curl -X GET "http://localhost:8000/health"Use Cases:
- Load balancer health checks
- Uptime monitoring services
- Docker health checks
- Quick availability verification
Purpose: Comprehensive health check with dependency status
Authentication: Not required
Rate Limit: 10 requests/minute
Response:
{
"status": "healthy",
"timestamp": "2025-11-22T10:30:00.000Z",
"dependencies": {
"supabase": {
"status": "healthy",
"response_time_ms": 45
},
"openrouter": {
"status": "healthy"
}
}
}Status Values:
healthy: All systems operationaldegraded: Some dependencies failing but service still functionalunhealthy: Critical failure, service unavailable
Example:
curl -X GET "http://localhost:8000/health/detailed"Use Cases:
- Detailed system diagnostics
- Dependency monitoring
- Troubleshooting connectivity issues
- Pre-deployment verification
Dependency Checks:
- Supabase: Queries users table to verify database connectivity
- OpenRouter: Checks AI service availability
All API endpoints are protected by rate limiting to prevent abuse and ensure fair usage.
| Endpoint Type | Limit | Window |
|---|---|---|
| Health checks | 10 requests | per minute |
| Standard API | 10 requests | per minute |
| AI endpoints | 5 requests | per minute |
The following endpoints have stricter rate limits due to computational cost:
/coach/feedback/{user_id}- 5 requests/minute/study/retry- 5 requests/minute/quiz/*(all quiz generation endpoints) - 5 requests/minute
When rate limited, the API returns:
Status Code: 429 Too Many Requests
Headers:
X-RateLimit-Limit: 10
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1700654400
Retry-After: 60
Response Body:
{
"error": "Rate limit exceeded",
"detail": "Too many requests. Please try again in 60 seconds."
}Client-Side Best Practices:
async function makeRequest(url, options) {
const response = await fetch(url, options);
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After');
console.log(`Rate limited. Retry after ${retryAfter} seconds`);
// Wait and retry
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
return makeRequest(url, options);
}
return response;
}Python Example:
import time
import requests
def make_request(url, headers):
response = requests.get(url, headers=headers)
if response.status_code == 429:
retry_after = int(response.headers.get('Retry-After', 60))
print(f"Rate limited. Waiting {retry_after} seconds...")
time.sleep(retry_after)
return make_request(url, headers)
return responseRate limiting is implemented using SlowAPI with in-memory storage:
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
limiter = Limiter(key_func=get_remote_address)
# Standard endpoints
@limiter.limit("10/minute")
async def standard_endpoint():
pass
# AI endpoints (stricter)
@limiter.limit("5/minute")
async def ai_endpoint():
passMost endpoints require JWT authentication via Supabase Auth.
Required Header:
Authorization: Bearer <JWT_TOKEN>
1. Sign Up / Login via Frontend:
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
// Sign up
const { data, error } = await supabase.auth.signUp({
email: 'user@example.com',
password: 'password123'
});
// Login
const { data, error } = await supabase.auth.signInWithPassword({
email: 'user@example.com',
password: 'password123'
});
// Get token
const token = data.session.access_token;2. Use Token in API Requests:
const response = await fetch('http://localhost:8000/progress/update', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
points: 50,
reason: 'quiz_completion'
})
});401 Unauthorized:
{
"detail": "Not authenticated"
}Causes:
- Missing Authorization header
- Invalid JWT token
- Expired JWT token
- Malformed token
403 Forbidden:
{
"detail": "Access forbidden"
}Causes:
- Attempting to access another user's data
- Insufficient permissions
All endpoints except /health and /health/detailed require authentication:
Protected:
- ✅ All
/progress/*endpoints - ✅ All
/coach/*endpoints - ✅ All
/study/*endpoints - ✅ All
/quiz/*endpoints - ✅ All
/achievements/*endpoints
Public:
- ❌
/health - ❌
/health/detailed - ❌
/docs(API documentation)
The backend validates JWT tokens using Supabase's JWT secret:
from utils.auth import verify_user
@router.post("/progress/update")
async def update_xp(
request: UpdateXPRequest,
current_user: dict = Depends(verify_user)
):
user_id = current_user['user_id']
# Process request...Token Claims:
sub: User ID (UUID)email: User emailexp: Expiration timestampiat: Issued at timestamp
-
Never expose tokens in logs:
# Bad logger.info(f"Token: {token}") # Good logger.info(f"User authenticated: {user_id}")
-
Use HTTPS in production:
- Tokens transmitted over HTTP can be intercepted
- Always use HTTPS for API requests
-
Implement token refresh:
// Refresh token before expiration const { data, error } = await supabase.auth.refreshSession(); const newToken = data.session.access_token;
-
Handle token expiration gracefully:
if (response.status === 401) { // Token expired, redirect to login window.location.href = '/login'; }
With Authentication and Rate Limiting:
# cURL with authentication
curl -X POST "http://localhost:8000/progress/update" \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"points": 100,
"reason": "quiz_completion",
"metadata": {"topic": "Python", "score": 85}
}'
# Check rate limit headers
curl -I "http://localhost:8000/health"JavaScript with Error Handling:
async function updateXP(points, reason, metadata) {
try {
const response = await fetch('/progress/update', {
method: 'POST',
headers: {
'Authorization': `Bearer ${getToken()}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ points, reason, metadata })
});
// Check rate limiting
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After');
throw new Error(`Rate limited. Retry in ${retryAfter}s`);
}
// Check authentication
if (response.status === 401) {
throw new Error('Authentication required');
}
// Check authorization
if (response.status === 403) {
throw new Error('Access forbidden');
}
const data = await response.json();
return data;
} catch (error) {
console.error('Failed to update XP:', error);
throw error;
}
}Python with Retry Logic:
import requests
import time
from typing import Dict, Any
def make_authenticated_request(
url: str,
token: str,
method: str = 'GET',
data: Dict[str, Any] = None,
max_retries: int = 3
) -> Dict[str, Any]:
"""Make authenticated API request with retry logic"""
headers = {
'Authorization': f'Bearer {token}',
'Content-Type': 'application/json'
}
for attempt in range(max_retries):
try:
if method == 'GET':
response = requests.get(url, headers=headers)
else:
response = requests.post(url, headers=headers, json=data)
# Handle rate limiting
if response.status_code == 429:
retry_after = int(response.headers.get('Retry-After', 60))
print(f"Rate limited. Waiting {retry_after}s...")
time.sleep(retry_after)
continue
# Handle authentication errors
if response.status_code == 401:
raise Exception('Authentication failed')
# Handle authorization errors
if response.status_code == 403:
raise Exception('Access forbidden')
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
if attempt == max_retries - 1:
raise
time.sleep(2 ** attempt) # Exponential backoff
raise Exception('Max retries exceeded')All requested backend API endpoints have been successfully implemented, tested, and documented. Ready for frontend integration.
- ✅ Health check endpoints documented
- ✅ Rate limiting behavior documented
- ✅ Authentication requirements clarified
- ✅ Request/response examples added
- ✅ Error handling guide updated