All checks were successful
Chore App Build, Test, and Push Docker Images / build-and-push (push) Successful in 3m23s
- Introduced a dual-token system for user authentication: a short-lived access token and a long-lived rotating refresh token. - Created a new RefreshToken model to manage refresh tokens securely. - Updated auth_api.py to handle login, refresh, and logout processes with the new token system. - Enhanced security measures including token rotation and theft detection. - Updated frontend to handle token refresh on 401 errors and adjusted SSE authentication. - Removed CORS middleware as it's unnecessary behind the nginx proxy. - Added tests to ensure functionality and security of the new token system.
74 lines
2.4 KiB
Python
74 lines
2.4 KiB
Python
from flask import Blueprint, request, jsonify
|
|
from api.utils import get_validated_user_id, admin_required
|
|
from db.tracking import get_tracking_events_by_child, get_tracking_events_by_user
|
|
from models.tracking_event import TrackingEvent
|
|
|
|
|
|
tracking_api = Blueprint('tracking_api', __name__)
|
|
|
|
|
|
@tracking_api.route('/admin/tracking', methods=['GET'])
|
|
@admin_required
|
|
def get_tracking():
|
|
"""
|
|
Admin endpoint to query tracking events with filters and pagination.
|
|
|
|
Query params:
|
|
- child_id: Filter by child ID (optional)
|
|
- user_id: Filter by user ID (optional, admin only)
|
|
- entity_type: Filter by entity type (task/reward/penalty) (optional)
|
|
- action: Filter by action type (activated/requested/redeemed/cancelled) (optional)
|
|
- limit: Max results (default 50, max 500)
|
|
- offset: Pagination offset (default 0)
|
|
"""
|
|
child_id = request.args.get('child_id')
|
|
filter_user_id = request.args.get('user_id')
|
|
entity_type = request.args.get('entity_type')
|
|
action = request.args.get('action')
|
|
limit = int(request.args.get('limit', 50))
|
|
offset = int(request.args.get('offset', 0))
|
|
|
|
# Validate limit
|
|
limit = min(max(limit, 1), 500)
|
|
offset = max(offset, 0)
|
|
|
|
# Validate filters
|
|
if entity_type and entity_type not in ['task', 'reward', 'penalty']:
|
|
return jsonify({'error': 'Invalid entity_type', 'code': 'INVALID_ENTITY_TYPE'}), 400
|
|
|
|
if action and action not in ['activated', 'requested', 'redeemed', 'cancelled']:
|
|
return jsonify({'error': 'Invalid action', 'code': 'INVALID_ACTION'}), 400
|
|
|
|
# Query tracking events
|
|
if child_id:
|
|
events, total = get_tracking_events_by_child(
|
|
child_id=child_id,
|
|
limit=limit,
|
|
offset=offset,
|
|
entity_type=entity_type,
|
|
action=action
|
|
)
|
|
elif filter_user_id:
|
|
events, total = get_tracking_events_by_user(
|
|
user_id=filter_user_id,
|
|
limit=limit,
|
|
offset=offset,
|
|
entity_type=entity_type
|
|
)
|
|
else:
|
|
return jsonify({
|
|
'error': 'Either child_id or user_id is required',
|
|
'code': 'MISSING_FILTER'
|
|
}), 400
|
|
|
|
# Convert to dict
|
|
events_data = [event.to_dict() for event in events]
|
|
|
|
return jsonify({
|
|
'tracking_events': events_data,
|
|
'total': total,
|
|
'limit': limit,
|
|
'offset': offset,
|
|
'count': len(events_data)
|
|
}), 200
|