Files
HarborForge.Backend/app/models/models.py
zhi 1c062ff4f1 BE-CAL-003: Add Agent model with status/heartbeat/exhausted fields
- New app/models/agent.py with Agent model, AgentStatus & ExhaustReason enums
- Agent has 1-to-1 FK to User, unique agent_id (OpenClaw $AGENT_ID),
  claw_identifier (OpenClaw instance, convention-matches MonitoredServer.identifier)
- Status fields: status (idle/on_call/busy/exhausted/offline), last_heartbeat
- Exhausted tracking: exhausted_at, recovery_at, exhaust_reason (rate_limit/billing)
- User model: added 'agent' back-reference (uselist=False)
- Schemas: AgentResponse, AgentStatusUpdate, UserCreate now accepts agent_id+claw_identifier
- UserResponse: includes agent_id when agent is bound
- Users router: create_user creates Agent record when agent_id+claw_identifier provided
- Auto-migration: CREATE TABLE agents in _migrate_schema()
- Startup imports: agent and calendar models registered
2026-03-30 20:47:44 +00:00

110 lines
3.8 KiB
Python

from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey, Enum, Boolean, JSON, Time
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from app.core.config import Base
from app.models.role_permission import Role
import enum
class TaskType(str, enum.Enum):
"""Task type enum."""
ISSUE = "issue"
MAINTENANCE = "maintenance"
RESEARCH = "research"
REVIEW = "review"
STORY = "story"
TEST = "test"
RESOLUTION = "resolution"
class TaskStatus(str, enum.Enum):
OPEN = "open"
PENDING = "pending"
UNDERGOING = "undergoing"
COMPLETED = "completed"
CLOSED = "closed"
class TaskPriority(str, enum.Enum):
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
CRITICAL = "critical"
class Comment(Base):
__tablename__ = "comments"
id = Column(Integer, primary_key=True, index=True)
content = Column(Text, nullable=False)
task_id = Column(Integer, ForeignKey("tasks.id"), nullable=False)
author_id = Column(Integer, ForeignKey("users.id"), nullable=False)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
author = relationship("User", back_populates="comments")
class Project(Base):
__tablename__ = "projects"
id = Column(Integer, primary_key=True, index=True)
name = Column(String(100), unique=True, nullable=False)
project_code = Column(String(16), unique=True, index=True, nullable=True)
owner_name = Column(String(128), nullable=False)
sub_projects = Column(String(512), nullable=True)
related_projects = Column(String(512), nullable=True)
repo = Column(String(512), nullable=True)
description = Column(Text, nullable=True)
created_at = Column(DateTime(timezone=True), server_default=func.now())
owner_id = Column(Integer, ForeignKey("users.id"), nullable=False)
members = relationship("ProjectMember", back_populates="project", cascade="all, delete-orphan")
owner = relationship("User", back_populates="owned_projects")
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
username = Column(String(50), unique=True, nullable=False)
email = Column(String(100), unique=True, nullable=False)
hashed_password = Column(String(255), nullable=True)
full_name = Column(String(100), nullable=True)
is_active = Column(Boolean, default=True)
is_admin = Column(Boolean, default=False)
role_id = Column(Integer, ForeignKey("roles.id"), nullable=True)
created_at = Column(DateTime(timezone=True), server_default=func.now())
role = relationship("Role", foreign_keys=[role_id])
owned_projects = relationship("Project", back_populates="owner")
comments = relationship("Comment", back_populates="author")
project_memberships = relationship("ProjectMember", back_populates="user")
agent = relationship("Agent", back_populates="user", uselist=False)
@property
def role_name(self):
return self.role.name if self.role else None
class ProjectMember(Base):
__tablename__ = "project_members"
id = Column(Integer, primary_key=True, index=True)
project_id = Column(Integer, ForeignKey("projects.id"), nullable=False)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
role_id = Column(Integer, ForeignKey("roles.id"), nullable=False)
role = relationship("Role")
project = relationship("Project", back_populates="members")
user = relationship("User", back_populates="project_memberships")
class ProjectCodeCounter(Base):
__tablename__ = "project_code_counters"
id = Column(Integer, primary_key=True, index=True)
prefix = Column(String(16), unique=True, index=True, nullable=False)
next_value = Column(Integer, default=0)