All checks were successful
Gitea Actions Demo / build-and-push (push) Successful in 24s
- Added tracking events for tasks, penalties, and rewards with timestamps. - Created new TinyDB table for tracking records to maintain audit history. - Developed backend API for querying tracking events with filters and pagination. - Implemented logging for tracking events with per-user rotating log files. - Added unit tests for tracking event creation, querying, and anonymization. - Deferred frontend changes for future implementation. - Established acceptance criteria and documentation for the tracking feature. feat: Introduce account deletion scheduler - Implemented a scheduler to delete accounts marked for deletion after a configurable threshold. - Added new fields to the User model to manage deletion status and attempts. - Created admin API endpoints for managing deletion thresholds and viewing the deletion queue. - Integrated error handling and logging for the deletion process. - Developed unit tests for the deletion scheduler and related API endpoints. - Documented the deletion process and acceptance criteria.
123 lines
4.1 KiB
Python
123 lines
4.1 KiB
Python
from flask import Blueprint, request, jsonify
|
|
from api.utils import get_validated_user_id
|
|
from db.tracking import get_tracking_events_by_child, get_tracking_events_by_user
|
|
from models.tracking_event import TrackingEvent
|
|
from functools import wraps
|
|
import jwt
|
|
from tinydb import Query
|
|
from db.db import users_db
|
|
from models.user import User
|
|
|
|
|
|
tracking_api = Blueprint('tracking_api', __name__)
|
|
|
|
|
|
def admin_required(f):
|
|
"""
|
|
Decorator to require admin role for endpoints.
|
|
"""
|
|
@wraps(f)
|
|
def decorated_function(*args, **kwargs):
|
|
# Get JWT token from cookie
|
|
token = request.cookies.get('token')
|
|
if not token:
|
|
return jsonify({'error': 'Authentication required', 'code': 'AUTH_REQUIRED'}), 401
|
|
|
|
try:
|
|
# Verify JWT token
|
|
payload = jwt.decode(token, 'supersecretkey', algorithms=['HS256'])
|
|
user_id = payload.get('user_id')
|
|
|
|
if not user_id:
|
|
return jsonify({'error': 'Invalid token', 'code': 'INVALID_TOKEN'}), 401
|
|
|
|
# Get user from database
|
|
Query_ = Query()
|
|
user_dict = users_db.get(Query_.id == user_id)
|
|
|
|
if not user_dict:
|
|
return jsonify({'error': 'User not found', 'code': 'USER_NOT_FOUND'}), 404
|
|
|
|
user = User.from_dict(user_dict)
|
|
|
|
# Check if user has admin role
|
|
if user.role != 'admin':
|
|
return jsonify({'error': 'Admin access required', 'code': 'ADMIN_REQUIRED'}), 403
|
|
|
|
# Store user_id in request context
|
|
request.admin_user_id = user_id
|
|
return f(*args, **kwargs)
|
|
|
|
except jwt.ExpiredSignatureError:
|
|
return jsonify({'error': 'Token expired', 'code': 'TOKEN_EXPIRED'}), 401
|
|
except jwt.InvalidTokenError:
|
|
return jsonify({'error': 'Invalid token', 'code': 'INVALID_TOKEN'}), 401
|
|
|
|
return decorated_function
|
|
|
|
|
|
@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
|