feat: Implement task and reward tracking feature
All checks were successful
Gitea Actions Demo / build-and-push (push) Successful in 24s
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.
This commit is contained in:
122
backend/api/tracking_api.py
Normal file
122
backend/api/tracking_api.py
Normal file
@@ -0,0 +1,122 @@
|
||||
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
|
||||
Reference in New Issue
Block a user