Files
sdb2-backend/app/api/v1/scheduler.py

231 lines
7.6 KiB
Python

"""API endpoints for scheduled task management."""
from typing import Annotated
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlmodel import select
from sqlmodel.ext.asyncio.session import AsyncSession
from app.core.database import get_db
from app.core.dependencies import (
get_admin_user,
get_current_active_user,
)
from app.core.services import get_global_scheduler_service
from app.models.scheduled_task import ScheduledTask, TaskStatus, TaskType
from app.models.user import User
from app.repositories.scheduled_task import ScheduledTaskRepository
from app.schemas.scheduler import (
ScheduledTaskCreate,
ScheduledTaskResponse,
ScheduledTaskUpdate,
TaskFilterParams,
)
from app.services.scheduler import SchedulerService
router = APIRouter(prefix="/scheduler")
def get_scheduler_service() -> SchedulerService:
"""Get the global scheduler service instance."""
return get_global_scheduler_service()
def get_task_filters(
status: Annotated[
TaskStatus | None, Query(description="Filter by task status"),
] = None,
task_type: Annotated[
TaskType | None, Query(description="Filter by task type"),
] = None,
limit: Annotated[int, Query(description="Maximum number of tasks to return")] = 50,
offset: Annotated[int, Query(description="Number of tasks to skip")] = 0,
) -> TaskFilterParams:
"""Create task filter parameters from query parameters."""
return TaskFilterParams(
status=status,
task_type=task_type,
limit=limit,
offset=offset,
)
@router.post("/tasks", response_model=ScheduledTaskResponse)
async def create_task(
task_data: ScheduledTaskCreate,
current_user: Annotated[User, Depends(get_current_active_user)],
scheduler_service: Annotated[SchedulerService, Depends(get_scheduler_service)],
) -> ScheduledTask:
"""Create a new scheduled task."""
try:
return await scheduler_service.create_task(
task_data=task_data,
user_id=current_user.id,
)
except Exception as e:
raise HTTPException(status_code=400, detail=str(e)) from e
@router.get("/tasks", response_model=list[ScheduledTaskResponse])
async def get_user_tasks(
filters: Annotated[TaskFilterParams, Depends(get_task_filters)],
current_user: Annotated[User, Depends(get_current_active_user)],
scheduler_service: Annotated[SchedulerService, Depends(get_scheduler_service)],
) -> list[ScheduledTask]:
"""Get user's scheduled tasks."""
return await scheduler_service.get_user_tasks(
user_id=current_user.id,
status=filters.status,
task_type=filters.task_type,
limit=filters.limit,
offset=filters.offset,
)
@router.get("/tasks/{task_id}", response_model=ScheduledTaskResponse)
async def get_task(
task_id: int,
current_user: Annotated[User, Depends(get_current_active_user)] = ...,
db_session: Annotated[AsyncSession, Depends(get_db)] = ...,
) -> ScheduledTask:
"""Get a specific scheduled task."""
repo = ScheduledTaskRepository(db_session)
task = await repo.get_by_id(task_id)
if not task:
raise HTTPException(status_code=404, detail="Task not found")
# Check if user owns the task or is admin
if task.user_id != current_user.id and not current_user.is_admin:
raise HTTPException(status_code=403, detail="Access denied")
return task
@router.patch("/tasks/{task_id}", response_model=ScheduledTaskResponse)
async def update_task(
task_id: int,
task_update: ScheduledTaskUpdate,
current_user: Annotated[User, Depends(get_current_active_user)] = ...,
db_session: Annotated[AsyncSession, Depends(get_db)] = ...,
) -> ScheduledTask:
"""Update a scheduled task."""
repo = ScheduledTaskRepository(db_session)
task = await repo.get_by_id(task_id)
if not task:
raise HTTPException(status_code=404, detail="Task not found")
# Check if user owns the task or is admin
if task.user_id != current_user.id and not current_user.is_admin:
raise HTTPException(status_code=403, detail="Access denied")
# Update task fields
update_data = task_update.model_dump(exclude_unset=True)
for field, value in update_data.items():
setattr(task, field, value)
return await repo.update(task)
@router.delete("/tasks/{task_id}")
async def cancel_task(
task_id: int,
current_user: Annotated[User, Depends(get_current_active_user)] = ...,
scheduler_service: Annotated[
SchedulerService, Depends(get_scheduler_service),
] = ...,
db_session: Annotated[AsyncSession, Depends(get_db)] = ...,
) -> dict:
"""Cancel a scheduled task."""
repo = ScheduledTaskRepository(db_session)
task = await repo.get_by_id(task_id)
if not task:
raise HTTPException(status_code=404, detail="Task not found")
# Check if user owns the task or is admin
if task.user_id != current_user.id and not current_user.is_admin:
raise HTTPException(status_code=403, detail="Access denied")
success = await scheduler_service.cancel_task(task_id)
if not success:
raise HTTPException(status_code=400, detail="Failed to cancel task")
return {"message": "Task cancelled successfully"}
# Admin-only endpoints
@router.get("/admin/tasks", response_model=list[ScheduledTaskResponse])
async def get_all_tasks(
status: Annotated[
TaskStatus | None, Query(description="Filter by task status"),
] = None,
task_type: Annotated[
TaskType | None, Query(description="Filter by task type"),
] = None,
limit: Annotated[
int | None, Query(description="Maximum number of tasks to return"),
] = 100,
offset: Annotated[
int | None, Query(description="Number of tasks to skip"),
] = 0,
_: Annotated[User, Depends(get_admin_user)] = ...,
db_session: Annotated[AsyncSession, Depends(get_db)] = ...,
) -> list[ScheduledTask]:
"""Get all scheduled tasks (admin only)."""
# Build query with pagination and filtering
statement = select(ScheduledTask)
if status:
statement = statement.where(ScheduledTask.status == status)
if task_type:
statement = statement.where(ScheduledTask.task_type == task_type)
statement = statement.order_by(ScheduledTask.scheduled_at.desc())
if offset:
statement = statement.offset(offset)
if limit:
statement = statement.limit(limit)
result = await db_session.exec(statement)
return list(result.all())
@router.get("/admin/system-tasks", response_model=list[ScheduledTaskResponse])
async def get_system_tasks(
status: Annotated[
TaskStatus | None, Query(description="Filter by task status"),
] = None,
task_type: Annotated[
TaskType | None, Query(description="Filter by task type"),
] = None,
_: Annotated[User, Depends(get_admin_user)] = ...,
db_session: Annotated[AsyncSession, Depends(get_db)] = ...,
) -> list[ScheduledTask]:
"""Get system tasks (admin only)."""
repo = ScheduledTaskRepository(db_session)
return await repo.get_system_tasks(status=status, task_type=task_type)
@router.post("/admin/system-tasks", response_model=ScheduledTaskResponse)
async def create_system_task(
task_data: ScheduledTaskCreate,
_: Annotated[User, Depends(get_admin_user)] = ...,
scheduler_service: Annotated[
SchedulerService, Depends(get_scheduler_service),
] = ...,
) -> ScheduledTask:
"""Create a system task (admin only)."""
try:
return await scheduler_service.create_task(
task_data=task_data,
user_id=None, # System task
)
except Exception as e:
raise HTTPException(status_code=400, detail=str(e)) from e