Lin / backend /api /schedules.py
Zelyanoth's picture
Implement immediate Celery Beat schedule updates
1d6d1e6
raw
history blame
11.6 kB
from flask import Blueprint, request, jsonify, current_app
from flask_jwt_extended import jwt_required, get_jwt_identity
from backend.services.schedule_service import ScheduleService
from backend.celery_tasks.schedule_loader import load_schedules_task
schedules_bp = Blueprint('schedules', __name__)
@schedules_bp.route('/', methods=['OPTIONS'])
@schedules_bp.route('', methods=['OPTIONS'])
def handle_options():
"""Handle OPTIONS requests for preflight CORS checks."""
return '', 200
@schedules_bp.route('/', methods=['GET'])
@schedules_bp.route('', methods=['GET'])
@jwt_required()
def get_schedules():
"""
Get all schedules for the current user.
Returns:
JSON: List of schedules
"""
try:
user_id = get_jwt_identity()
print(f"[DEBUG] get_schedules called for user_id: {user_id}")
# Check if Supabase client is initialized
if not hasattr(current_app, 'supabase') or current_app.supabase is None:
print("[ERROR] Supabase client not initialized")
# Add CORS headers to error response
response_data = jsonify({
'success': False,
'message': 'Database connection not initialized'
})
response_data.headers.add('Access-Control-Allow-Origin', 'http://localhost:3000')
response_data.headers.add('Access-Control-Allow-Credentials', 'true')
return response_data, 500
schedule_service = ScheduleService()
schedules = schedule_service.get_user_schedules(user_id)
print(f"[DEBUG] Found {len(schedules)} schedules for user {user_id}")
# Add CORS headers explicitly
response_data = jsonify({
'success': True,
'schedules': schedules
})
response_data.headers.add('Access-Control-Allow-Origin', 'http://localhost:3000')
response_data.headers.add('Access-Control-Allow-Credentials', 'true')
return response_data, 200
except Exception as e:
print(f"[ERROR] Get schedules error: {str(e)}")
import traceback
print(f"[ERROR] Full traceback: {traceback.format_exc()}")
current_app.logger.error(f"Get schedules error: {str(e)}")
# Add CORS headers to error response
response_data = jsonify({
'success': False,
'message': f'An error occurred while fetching schedules: {str(e)}',
'schedules': [] # Return empty array on error
})
response_data.headers.add('Access-Control-Allow-Origin', 'http://localhost:3000')
response_data.headers.add('Access-Control-Allow-Credentials', 'true')
return response_data, 500
@schedules_bp.route('/', methods=['POST'])
@schedules_bp.route('', methods=['POST'])
@jwt_required()
def create_schedule():
"""
Create a new schedule for the current user.
Request Body:
social_network (str): Social account ID
schedule_time (str): Schedule time in format "HH:MM"
days (List[str]): List of days to schedule
Returns:
JSON: Create schedule result
"""
try:
user_id = get_jwt_identity()
data = request.get_json()
# Validate required fields
required_fields = ['social_network', 'schedule_time', 'days']
if not data or not all(k in data for k in required_fields):
# Add CORS headers to error response
response_data = jsonify({
'success': False,
'message': 'Social network, schedule time, and days are required'
})
response_data.headers.add('Access-Control-Allow-Origin', 'http://localhost:3000')
response_data.headers.add('Access-Control-Allow-Credentials', 'true')
return response_data, 400
social_network = data['social_network']
schedule_time = data['schedule_time']
days = data['days']
# Validate days format
valid_days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
if not isinstance(days, list) or not all(day in valid_days for day in days):
# Add CORS headers to error response
response_data = jsonify({
'success': False,
'message': 'Days must be a list of valid day names'
})
response_data.headers.add('Access-Control-Allow-Origin', 'http://localhost:3000')
response_data.headers.add('Access-Control-Allow-Credentials', 'true')
return response_data, 400
# Validate time format
try:
hour, minute = map(int, schedule_time.split(':'))
if hour < 0 or hour > 23 or minute < 0 or minute > 59:
raise ValueError
except ValueError:
# Add CORS headers to error response
response_data = jsonify({
'success': False,
'message': 'Schedule time must be in format HH:MM (24-hour format)'
})
response_data.headers.add('Access-Control-Allow-Origin', 'http://localhost:3000')
response_data.headers.add('Access-Control-Allow-Credentials', 'true')
return response_data, 400
# Create schedule using schedule service
schedule_service = ScheduleService()
result = schedule_service.create_schedule(user_id, social_network, schedule_time, days)
if result['success']:
# Trigger immediate Celery Beat schedule update
try:
print("[INFO] Triggering immediate Celery Beat schedule update...")
# Execute the schedule loader task immediately to update Celery Beat
celery_result = load_schedules_task.delay()
print(f"[INFO] Celery Beat update task queued: {celery_result.id}")
# Add the task ID to the response for tracking
result['celery_update_task_id'] = celery_result.id
result['message'] += ' (Scheduler updated immediately)'
except Exception as e:
print(f"[WARNING] Failed to trigger immediate Celery update: {str(e)}")
# Don't fail the schedule creation if Celery update fails
result['message'] += ' (Note: Scheduler update will occur in 5 minutes)'
# Add CORS headers to success response
response_data = jsonify(result)
response_data.headers.add('Access-Control-Allow-Origin', 'http://localhost:3000')
response_data.headers.add('Access-Control-Allow-Credentials', 'true')
return response_data, 201
else:
# Add CORS headers to error response
response_data = jsonify(result)
response_data.headers.add('Access-Control-Allow-Origin', 'http://localhost:3000')
response_data.headers.add('Access-Control-Allow-Credentials', 'true')
return response_data, 400
except Exception as e:
current_app.logger.error(f"Create schedule error: {str(e)}")
# Add CORS headers to error response
response_data = jsonify({
'success': False,
'message': f'An error occurred while creating schedule: {str(e)}'
})
response_data.headers.add('Access-Control-Allow-Origin', 'http://localhost:3000')
response_data.headers.add('Access-Control-Allow-Credentials', 'true')
return response_data, 500
@schedules_bp.route('/<schedule_id>', methods=['OPTIONS'])
def handle_schedule_options(schedule_id):
"""Handle OPTIONS requests for preflight CORS checks for specific schedule."""
return '', 200
@schedules_bp.route('/<schedule_id>', methods=['DELETE'])
@jwt_required()
def delete_schedule(schedule_id):
"""
Delete a schedule.
Path Parameters:
schedule_id (str): Schedule ID
Returns:
JSON: Delete schedule result
"""
try:
user_id = get_jwt_identity()
# Verify the schedule belongs to the user
response = (
current_app.supabase
.table("Scheduling")
.select("Social_network(id_utilisateur)")
.eq("id", schedule_id)
.execute()
)
if not response.data:
# Add CORS headers to error response
response_data = jsonify({
'success': False,
'message': 'Schedule not found'
})
response_data.headers.add('Access-Control-Allow-Origin', 'http://localhost:3000')
response_data.headers.add('Access-Control-Allow-Credentials', 'true')
return response_data, 404
schedule = response.data[0]
if schedule.get('Social_network', {}).get('id_utilisateur') != user_id:
# Add CORS headers to error response
response_data = jsonify({
'success': False,
'message': 'Unauthorized to delete this schedule'
})
response_data.headers.add('Access-Control-Allow-Origin', 'http://localhost:3000')
response_data.headers.add('Access-Control-Allow-Credentials', 'true')
return response_data, 403
# Delete schedule using schedule service
schedule_service = ScheduleService()
result = schedule_service.delete_schedule(schedule_id)
if result['success']:
# Trigger immediate Celery Beat schedule update
try:
print("[INFO] Triggering immediate Celery Beat schedule update after deletion...")
# Execute the schedule loader task immediately to update Celery Beat
celery_result = load_schedules_task.delay()
print(f"[INFO] Celery Beat update task queued: {celery_result.id}")
# Add the task ID to the response for tracking
result['celery_update_task_id'] = celery_result.id
result['message'] += ' (Scheduler updated immediately)'
except Exception as e:
print(f"[WARNING] Failed to trigger immediate Celery update: {str(e)}")
# Don't fail the schedule deletion if Celery update fails
result['message'] += ' (Note: Scheduler update will occur in 5 minutes)'
# Add CORS headers to success response
response_data = jsonify(result)
response_data.headers.add('Access-Control-Allow-Origin', 'http://localhost:3000')
response_data.headers.add('Access-Control-Allow-Credentials', 'true')
return response_data, 200
else:
# Add CORS headers to error response
response_data = jsonify(result)
response_data.headers.add('Access-Control-Allow-Origin', 'http://localhost:3000')
response_data.headers.add('Access-Control-Allow-Credentials', 'true')
return response_data, 404
except Exception as e:
current_app.logger.error(f"Delete schedule error: {str(e)}")
# Add CORS headers to error response
response_data = jsonify({
'success': False,
'message': f'An error occurred while deleting schedule: {str(e)}'
})
response_data.headers.add('Access-Control-Allow-Origin', 'http://localhost:3000')
response_data.headers.add('Access-Control-Allow-Credentials', 'true')
return response_data, 500