132 lines
4.8 KiB
Python
132 lines
4.8 KiB
Python
|
|
from fastapi import APIRouter, HTTPException, Depends
|
|
from pydantic import BaseModel
|
|
from sqlalchemy.orm import Session
|
|
from typing import List, Optional
|
|
from datetime import datetime, timedelta
|
|
import json
|
|
import logging
|
|
|
|
from ..models.scheduled_job import ScheduledJob
|
|
from ..services.postgresql_manager import PostgreSQLManager
|
|
from ..utils.config import config
|
|
from ..services.scheduler import scheduler
|
|
|
|
router = APIRouter()
|
|
logger = logging.getLogger(__name__)
|
|
|
|
def get_db():
|
|
db_manager = PostgreSQLManager(config.DATABASE_URL)
|
|
with db_manager.get_db_session() as session:
|
|
yield session
|
|
|
|
class ScheduledJobResponse(BaseModel):
|
|
id: int
|
|
job_type: str
|
|
name: str
|
|
interval_minutes: int
|
|
enabled: bool
|
|
last_run: Optional[datetime]
|
|
next_run: Optional[datetime]
|
|
params: Optional[str]
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
class JobUpdateRequest(BaseModel):
|
|
interval_minutes: Optional[int] = None
|
|
enabled: Optional[bool] = None
|
|
params: Optional[dict] = None
|
|
|
|
@router.get("/scheduling/jobs", response_model=List[ScheduledJobResponse])
|
|
def list_scheduled_jobs(db: Session = Depends(get_db)):
|
|
"""List all scheduled jobs."""
|
|
jobs = db.query(ScheduledJob).order_by(ScheduledJob.id).all()
|
|
return jobs
|
|
|
|
@router.put("/scheduling/jobs/{job_id}", response_model=ScheduledJobResponse)
|
|
def update_scheduled_job(job_id: int, request: JobUpdateRequest, db: Session = Depends(get_db)):
|
|
"""Update a scheduled job's interval or enabled status."""
|
|
job = db.query(ScheduledJob).filter(ScheduledJob.id == job_id).first()
|
|
if not job:
|
|
raise HTTPException(status_code=404, detail="Job not found")
|
|
|
|
if request.interval_minutes is not None:
|
|
if request.interval_minutes < 1:
|
|
raise HTTPException(status_code=400, detail="Interval must be at least 1 minute")
|
|
job.interval_minutes = request.interval_minutes
|
|
|
|
# If enabled, update next_run based on new interval if it's far in future?
|
|
# Actually, standard behavior: next_run should be recalculated from last_run + new interval
|
|
# OR just leave it. If we shorten it, we might want it to run sooner.
|
|
# Let's recalculate next_run if it exists.
|
|
if job.last_run:
|
|
job.next_run = job.last_run + timedelta(minutes=job.interval_minutes)
|
|
else:
|
|
# If never run, next_run should be Now if enabled?
|
|
# Or keep existing next_run?
|
|
# If next_run is null and enabled, scheduler picks it up immediately.
|
|
pass
|
|
|
|
if request.enabled is not None:
|
|
job.enabled = request.enabled
|
|
if job.enabled and job.next_run is None:
|
|
# If re-enabling and no next run, set to now
|
|
job.next_run = datetime.now()
|
|
|
|
if request.params is not None:
|
|
job.params = json.dumps(request.params)
|
|
|
|
db.commit()
|
|
db.refresh(job)
|
|
return job
|
|
|
|
class JobCreateRequest(BaseModel):
|
|
job_type: str
|
|
name: str
|
|
interval_minutes: int
|
|
params: Optional[dict] = {}
|
|
enabled: Optional[bool] = True
|
|
|
|
@router.post("/scheduling/jobs", response_model=ScheduledJobResponse)
|
|
def create_scheduled_job(request: JobCreateRequest, db: Session = Depends(get_db)):
|
|
"""Create a new scheduled job."""
|
|
# Validate job_type
|
|
from ..services.scheduler import scheduler
|
|
if request.job_type not in scheduler.TASK_MAP:
|
|
raise HTTPException(status_code=400, detail=f"Invalid job_type. Must be one of: {list(scheduler.TASK_MAP.keys())}")
|
|
|
|
new_job = ScheduledJob(
|
|
job_type=request.job_type,
|
|
name=request.name,
|
|
interval_minutes=request.interval_minutes,
|
|
params=json.dumps(request.params) if request.params else "{}",
|
|
enabled=request.enabled,
|
|
next_run=datetime.now() if request.enabled else None
|
|
)
|
|
|
|
try:
|
|
db.add(new_job)
|
|
db.commit()
|
|
db.refresh(new_job)
|
|
return new_job
|
|
except Exception as e:
|
|
db.rollback()
|
|
logger.error(f"Failed to create job: {e}")
|
|
# Check for unique constraint on job_type if we enforced it?
|
|
# The model has job_type unique=True. This might be a problem if we want multiple of same type?
|
|
# User wants "new scheduled tasks" with "variables" -> implies multiple of same type (e.g. sync fitbit 10 days vs 30 days).
|
|
# We need to remove unique=True from ScheduledJob model if it exists!
|
|
raise HTTPException(status_code=400, detail=f"Failed to create job: {str(e)}")
|
|
|
|
@router.delete("/scheduling/jobs/{job_id}", status_code=204)
|
|
def delete_scheduled_job(job_id: int, db: Session = Depends(get_db)):
|
|
"""Delete a scheduled job."""
|
|
job = db.query(ScheduledJob).filter(ScheduledJob.id == job_id).first()
|
|
if not job:
|
|
raise HTTPException(status_code=404, detail="Job not found")
|
|
|
|
db.delete(job)
|
|
db.commit()
|
|
return None
|