Files
ss-tools/backend/src/api/routes/translate/_schedule_routes.py
busya 306c5ae742 semantics: complete DEF-to-region migration, fix regressions
- Convert legacy [DEF🆔Type] anchors to #region/#endregion across 329 files
- Reinstate _normalize_timestamp_value in sql_generator.py
- Fix MarkerLogger→logger migration in events.py (molecular CoT markers)
- Fix dataset_review orchestrator dependencies (_build_execution_snapshot)
- Fix config_manager stale-record deletion (moved to save path only)
- Add 77 missing [/DEF:] closers in 5 unbalanced test files
- Update assistant_chat.integration.test.js for #region format
- Apply molecular-cot-logging markers (REASON/REFLECT/EXPLORE) via logger.* methods
2026-05-12 23:54:55 +03:00

234 lines
9.8 KiB
Python

# #region TranslateScheduleRoutesModule [C:3] [TYPE Module] [SEMANTICS api, routes, translate, schedule]
# @BRIEF Translation Schedule management routes.
# @LAYER: API
from fastapi import APIRouter, Depends, HTTPException, status, Query
from typing import Any, Dict, List, Optional
from sqlalchemy.orm import Session
from ....core.database import get_db
from ....core.logger import logger, belief_scope
from ....schemas.auth import User
from ....dependencies import get_current_user, has_permission, get_config_manager
from ....core.config_manager import ConfigManager
from ....plugins.translate.scheduler import TranslationScheduler
from ....schemas.translate import ScheduleConfig, ScheduleResponse
from ._router import router
# ============================================================
# Schedule
# ============================================================
# #region get_schedule [TYPE Function]
# @BRIEF Get the schedule for a translation job.
# @PRE: User has translate.schedule.view permission.
# @POST: Returns the schedule configuration.
@router.get("/jobs/{job_id}/schedule")
async def get_schedule(
job_id: str,
current_user: User = Depends(get_current_user),
_ = Depends(has_permission("translate.schedule", "VIEW")),
db: Session = Depends(get_db),
config_manager: ConfigManager = Depends(get_config_manager),
):
"""Get schedule for a translation job."""
logger.info(f"[translate_routes][get_schedule] Job: {job_id}, User: {current_user.username}")
try:
scheduler = TranslationScheduler(db, config_manager, current_user.username)
schedule = scheduler.get_schedule(job_id)
return {
"id": schedule.id,
"job_id": schedule.job_id,
"cron_expression": schedule.cron_expression,
"timezone": schedule.timezone,
"is_active": schedule.is_active,
"last_run_at": schedule.last_run_at.isoformat() if schedule.last_run_at else None,
"next_run_at": schedule.next_run_at.isoformat() if schedule.next_run_at else None,
"created_by": schedule.created_by,
"created_at": schedule.created_at.isoformat() if schedule.created_at else None,
"updated_at": schedule.updated_at.isoformat() if schedule.updated_at else None,
}
except ValueError as e:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e))
# #endregion get_schedule
# #region set_schedule [TYPE Function]
# @BRIEF Set or update the schedule for a translation job.
# @PRE: User has translate.schedule.manage permission.
# @POST: Schedule is created or updated.
@router.put("/jobs/{job_id}/schedule")
async def set_schedule(
job_id: str,
payload: ScheduleConfig,
current_user: User = Depends(get_current_user),
_ = Depends(has_permission("translate.schedule", "MANAGE")),
db: Session = Depends(get_db),
config_manager: ConfigManager = Depends(get_config_manager),
):
"""Set or update schedule for a translation job."""
logger.info(f"[translate_routes][set_schedule] Job: {job_id}, User: {current_user.username}")
try:
scheduler = TranslationScheduler(db, config_manager, current_user.username)
# Check if schedule already exists
from ...models.translate import TranslationSchedule
existing = db.query(TranslationSchedule).filter(
TranslationSchedule.job_id == job_id
).first()
if existing:
schedule = scheduler.update_schedule(
job_id,
cron_expression=payload.cron_expression,
timezone=payload.timezone,
is_active=payload.is_active,
)
else:
schedule = scheduler.create_schedule(
job_id,
cron_expression=payload.cron_expression,
timezone=payload.timezone,
is_active=payload.is_active,
)
# Register with APScheduler via SchedulerService
from ...dependencies import get_scheduler_service
sched_svc = get_scheduler_service()
sched_svc.add_translation_job(
schedule_id=schedule.id,
job_id=job_id,
cron_expression=schedule.cron_expression,
timezone=schedule.timezone,
)
return {
"id": schedule.id,
"job_id": schedule.job_id,
"cron_expression": schedule.cron_expression,
"timezone": schedule.timezone,
"is_active": schedule.is_active,
"last_run_at": schedule.last_run_at.isoformat() if schedule.last_run_at else None,
"created_by": schedule.created_by,
"created_at": schedule.created_at.isoformat() if schedule.created_at else None,
"updated_at": schedule.updated_at.isoformat() if schedule.updated_at else None,
}
except ValueError as e:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
# #endregion set_schedule
# #region enable_schedule [TYPE Function]
# @BRIEF Enable a schedule for a translation job.
# @PRE: User has translate.schedule.manage permission.
# @POST: Schedule is enabled.
@router.post("/jobs/{job_id}/schedule/enable")
async def enable_schedule(
job_id: str,
current_user: User = Depends(get_current_user),
_ = Depends(has_permission("translate.schedule", "MANAGE")),
db: Session = Depends(get_db),
config_manager: ConfigManager = Depends(get_config_manager),
):
"""Enable a schedule for a translation job."""
logger.info(f"[translate_routes][enable_schedule] Job: {job_id}, User: {current_user.username}")
try:
scheduler = TranslationScheduler(db, config_manager, current_user.username)
schedule = scheduler.set_schedule_active(job_id, is_active=True)
from ...dependencies import get_scheduler_service
sched_svc = get_scheduler_service()
sched_svc.add_translation_job(
schedule_id=schedule.id,
job_id=job_id,
cron_expression=schedule.cron_expression,
timezone=schedule.timezone,
)
return {"status": "enabled", "job_id": job_id}
except ValueError as e:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e))
# #endregion enable_schedule
# #region disable_schedule [TYPE Function]
# @BRIEF Disable a schedule for a translation job.
# @PRE: User has translate.schedule.manage permission.
# @POST: Schedule is disabled.
@router.post("/jobs/{job_id}/schedule/disable")
async def disable_schedule(
job_id: str,
current_user: User = Depends(get_current_user),
_ = Depends(has_permission("translate.schedule", "MANAGE")),
db: Session = Depends(get_db),
config_manager: ConfigManager = Depends(get_config_manager),
):
"""Disable a schedule for a translation job."""
logger.info(f"[translate_routes][disable_schedule] Job: {job_id}, User: {current_user.username}")
try:
scheduler = TranslationScheduler(db, config_manager, current_user.username)
scheduler.set_schedule_active(job_id, is_active=False)
from ...dependencies import get_scheduler_service
sched_svc = get_scheduler_service()
sched_svc.remove_translation_job(schedule_id=job_id)
return {"status": "disabled", "job_id": job_id}
except ValueError as e:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e))
# #endregion disable_schedule
# #region delete_schedule [TYPE Function]
# @BRIEF Delete the schedule for a translation job.
# @PRE: User has translate.schedule.manage permission.
# @POST: Schedule is removed.
@router.delete("/jobs/{job_id}/schedule", status_code=status.HTTP_204_NO_CONTENT)
async def delete_schedule(
job_id: str,
current_user: User = Depends(get_current_user),
_ = Depends(has_permission("translate.schedule", "MANAGE")),
db: Session = Depends(get_db),
config_manager: ConfigManager = Depends(get_config_manager),
):
"""Delete schedule for a translation job."""
logger.info(f"[translate_routes][delete_schedule] Job: {job_id}, User: {current_user.username}")
try:
scheduler = TranslationScheduler(db, config_manager, current_user.username)
scheduler.delete_schedule(job_id)
from ...dependencies import get_scheduler_service
sched_svc = get_scheduler_service()
sched_svc.remove_translation_job(schedule_id=job_id)
return None
except ValueError as e:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e))
# #endregion delete_schedule
# #region get_next_executions [TYPE Function]
# @BRIEF Preview next N executions for a job's schedule.
# @PRE: User has translate.schedule.view permission.
# @POST: Returns next execution times.
@router.get("/jobs/{job_id}/schedule/next-executions")
async def get_next_executions(
job_id: str,
n: int = Query(3, ge=1, le=10),
current_user: User = Depends(get_current_user),
_ = Depends(has_permission("translate.schedule", "VIEW")),
db: Session = Depends(get_db),
config_manager: ConfigManager = Depends(get_config_manager),
):
"""Preview next N executions for a job's schedule."""
logger.info(f"[translate_routes][get_next_executions] Job: {job_id}, User: {current_user.username}")
try:
scheduler = TranslationScheduler(db, config_manager, current_user.username)
schedule = scheduler.get_schedule(job_id)
next_times = TranslationScheduler.get_next_executions(
schedule.cron_expression, schedule.timezone, n=n
)
return {
"job_id": job_id,
"cron_expression": schedule.cron_expression,
"timezone": schedule.timezone,
"next_executions": next_times,
}
except ValueError as e:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e))
# #endregion get_next_executions
# #endregion TranslateScheduleRoutesModule