""" SQLAlchemy ORM models for Foxy Dev Team. """ import enum from datetime import datetime, timezone from typing import Optional, List from sqlalchemy import ( String, Text, Integer, DateTime, ForeignKey, Enum, JSON ) from sqlalchemy.orm import Mapped, mapped_column, relationship from app.database import Base # ─── Enums ───────────────────────────────────────────────────────────────────── class ProjectStatus(str, enum.Enum): PENDING = "PENDING" AWAITING_CONDUCTOR = "AWAITING_CONDUCTOR" CONDUCTOR_RUNNING = "CONDUCTOR_RUNNING" AWAITING_ARCHITECT = "AWAITING_ARCHITECT" ARCHITECT_RUNNING = "ARCHITECT_RUNNING" AWAITING_DEV = "AWAITING_DEV" DEV_RUNNING = "DEV_RUNNING" AWAITING_UIUX = "AWAITING_UIUX" UIUX_RUNNING = "UIUX_RUNNING" AWAITING_QA = "AWAITING_QA" QA_RUNNING = "QA_RUNNING" AWAITING_DEPLOY = "AWAITING_DEPLOY" DEPLOY_RUNNING = "DEPLOY_RUNNING" COMPLETED = "COMPLETED" FAILED = "FAILED" PAUSED = "PAUSED" class WorkflowType(str, enum.Enum): SOFTWARE_DESIGN = "SOFTWARE_DESIGN" SYSADMIN_DEBUG = "SYSADMIN_DEBUG" DEVOPS_SETUP = "DEVOPS_SETUP" SYSADMIN_ADJUST = "SYSADMIN_ADJUST" class TaskStatus(str, enum.Enum): PENDING = "PENDING" IN_PROGRESS = "IN_PROGRESS" IN_REVIEW = "IN_REVIEW" REJECTED = "REJECTED" READY_FOR_DEPLOY = "READY_FOR_DEPLOY" DONE = "DONE" BLOCKED = "BLOCKED" class TaskType(str, enum.Enum): BACKEND = "BACKEND" FRONTEND = "FRONTEND" INFRA = "INFRA" TEST = "TEST" DESIGN = "DESIGN" class TaskPriority(str, enum.Enum): P1 = "P1" P2 = "P2" P3 = "P3" class AgentExecutionStatus(str, enum.Enum): RUNNING = "RUNNING" SUCCESS = "SUCCESS" FAILED = "FAILED" TIMEOUT = "TIMEOUT" def _utcnow() -> datetime: return datetime.now(timezone.utc) # ─── Models ──────────────────────────────────────────────────────────────────── class Project(Base): __tablename__ = "projects" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) name: Mapped[str] = mapped_column(String(200), nullable=False) slug: Mapped[str] = mapped_column(String(200), unique=True, nullable=False, index=True) description: Mapped[str] = mapped_column(Text, default="") status: Mapped[ProjectStatus] = mapped_column( Enum(ProjectStatus), default=ProjectStatus.PENDING, nullable=False ) workflow_type: Mapped[WorkflowType] = mapped_column( Enum(WorkflowType), default=WorkflowType.SOFTWARE_DESIGN, nullable=False ) test_mode: Mapped[bool] = mapped_column(default=False) gitea_repo: Mapped[Optional[str]] = mapped_column(String(500), nullable=True) deployment_target: Mapped[Optional[str]] = mapped_column(String(200), nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_utcnow) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=_utcnow, onupdate=_utcnow ) # Relationships tasks: Mapped[List["Task"]] = relationship( back_populates="project", cascade="all, delete-orphan", lazy="selectin" ) audit_logs: Mapped[List["AuditLog"]] = relationship( back_populates="project", cascade="all, delete-orphan", lazy="selectin" ) agent_executions: Mapped[List["AgentExecution"]] = relationship( back_populates="project", cascade="all, delete-orphan", lazy="selectin" ) class Task(Base): __tablename__ = "tasks" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) project_id: Mapped[int] = mapped_column(ForeignKey("projects.id"), nullable=False) task_id: Mapped[str] = mapped_column(String(50), nullable=False) # e.g. TASK-001 type: Mapped[TaskType] = mapped_column(Enum(TaskType), default=TaskType.BACKEND) title: Mapped[str] = mapped_column(String(500), nullable=False) priority: Mapped[TaskPriority] = mapped_column(Enum(TaskPriority), default=TaskPriority.P3) assigned_to: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) status: Mapped[TaskStatus] = mapped_column( Enum(TaskStatus), default=TaskStatus.PENDING, nullable=False ) dependencies: Mapped[Optional[dict]] = mapped_column(JSON, default=list) acceptance_criteria: Mapped[Optional[str]] = mapped_column(Text, nullable=True) agent_payloads: Mapped[Optional[dict]] = mapped_column(JSON, default=dict) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_utcnow) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=_utcnow, onupdate=_utcnow ) # Relationships project: Mapped["Project"] = relationship(back_populates="tasks") class AgentExecution(Base): __tablename__ = "agent_executions" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) project_id: Mapped[int] = mapped_column(ForeignKey("projects.id"), nullable=False) agent_name: Mapped[str] = mapped_column(String(100), nullable=False) status: Mapped[AgentExecutionStatus] = mapped_column( Enum(AgentExecutionStatus), default=AgentExecutionStatus.RUNNING ) pid: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) started_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_utcnow) finished_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True) exit_code: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) error_output: Mapped[Optional[str]] = mapped_column(Text, nullable=True) # Relationships project: Mapped["Project"] = relationship(back_populates="agent_executions") class AuditLog(Base): __tablename__ = "audit_logs" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) project_id: Mapped[int] = mapped_column(ForeignKey("projects.id"), nullable=False) timestamp: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=_utcnow) agent: Mapped[str] = mapped_column(String(100), nullable=False) action: Mapped[str] = mapped_column(String(100), nullable=False) target: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) message: Mapped[Optional[str]] = mapped_column(Text, nullable=True) source: Mapped[str] = mapped_column(String(100), default="api") # Relationships project: Mapped["Project"] = relationship(back_populates="audit_logs")