from __future__ import annotations import enum from datetime import datetime from typing import Any from sqlalchemy import JSON, Boolean, DateTime, Enum, ForeignKey, Integer, String, Text, func from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship class Base(DeclarativeBase): pass class Job(Base): __tablename__ = "jobs" id: Mapped[str] = mapped_column(String, primary_key=True) cron_expr: Mapped[str] = mapped_column(String, nullable=False) handler_path: Mapped[str] = mapped_column(String, nullable=False) public_cfg: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict, nullable=False) # 密文 token(Fernet 加密后的字符串) secret_cfg: Mapped[str] = mapped_column(Text, default="", nullable=False) enabled: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) last_run_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), nullable=False) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False ) logs: Mapped[list["JobLog"]] = relationship(back_populates="job", cascade="all, delete-orphan") class JobStatus(str, enum.Enum): SUCCESS = "SUCCESS" FAILURE = "FAILURE" RETRY = "RETRY" class JobLog(Base): __tablename__ = "job_logs" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) job_id: Mapped[str] = mapped_column(ForeignKey("jobs.id"), index=True, nullable=False) status: Mapped[JobStatus] = mapped_column( Enum(JobStatus, native_enum=False, length=16), nullable=False, ) snapshot_params: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict, nullable=False) message: Mapped[str] = mapped_column(Text, default="", nullable=False) traceback: Mapped[str] = mapped_column(Text, default="", nullable=False) # 本次执行期间捕获到的完整运行日志(可能很长,按上层捕获器做截断) run_log: Mapped[str] = mapped_column(Text, default="", nullable=False) celery_task_id: Mapped[str] = mapped_column(String, default="", nullable=False) attempt: Mapped[int] = mapped_column(Integer, default=0, nullable=False) started_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), nullable=False) finished_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) job: Mapped[Job] = relationship(back_populates="logs")