80 lines
2.7 KiB
Python
80 lines
2.7 KiB
Python
from datetime import UTC, datetime
|
|
|
|
from sqlalchemy.dialects.postgresql import JSONB
|
|
|
|
from app import db
|
|
|
|
|
|
class Epic(db.Model):
|
|
"""Epic model for tracking large features across multiple cards"""
|
|
|
|
__tablename__ = "epics"
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
name = db.Column(db.String(200), nullable=False, index=True)
|
|
description = db.Column(db.Text)
|
|
content = db.Column(JSONB) # Rich text content (Slate.js JSON)
|
|
color = db.Column(db.String(7)) # Hex color for epic badge
|
|
closed = db.Column(db.Boolean, default=False, index=True)
|
|
pos = db.Column(db.Float) # Position for sorting in epic list
|
|
depth_limit = db.Column(db.Integer, default=5) # Max nesting depth
|
|
|
|
# Foreign keys
|
|
board_id = db.Column(
|
|
db.Integer,
|
|
db.ForeignKey("boards.id", ondelete="CASCADE"),
|
|
nullable=False,
|
|
index=True,
|
|
)
|
|
parent_epic_id = db.Column(
|
|
db.Integer, db.ForeignKey("epics.id", ondelete="SET NULL")
|
|
)
|
|
|
|
# Timestamps
|
|
date_last_activity = db.Column(db.DateTime)
|
|
created_at = db.Column(db.DateTime, default=lambda: datetime.now(UTC))
|
|
updated_at = db.Column(
|
|
db.DateTime,
|
|
default=lambda: datetime.now(UTC),
|
|
onupdate=lambda: datetime.now(UTC),
|
|
)
|
|
|
|
# JSON fields for metrics
|
|
metrics = db.Column(JSONB) # {"card_count": 10}
|
|
|
|
# Relationships
|
|
board = db.relationship("Board", backref="epics")
|
|
parent_epic = db.relationship("Epic", remote_side=[id], backref="child_epics")
|
|
cards = db.relationship("Card", backref="epic", cascade="all, delete-orphan")
|
|
attachments = db.relationship(
|
|
"FileAttachment",
|
|
foreign_keys="FileAttachment.attachable_id",
|
|
primaryjoin="""and_(FileAttachment.attachable_id == Epic.id,
|
|
FileAttachment.attachable_type == 'Epic')""",
|
|
cascade="all, delete-orphan",
|
|
lazy="dynamic",
|
|
)
|
|
|
|
def to_dict(self):
|
|
"""Convert epic to dictionary"""
|
|
return {
|
|
"id": self.id,
|
|
"name": self.name,
|
|
"description": self.description,
|
|
"content": self.content,
|
|
"color": self.color,
|
|
"closed": self.closed,
|
|
"pos": self.pos,
|
|
"depth_limit": self.depth_limit,
|
|
"board_id": self.board_id,
|
|
"parent_epic_id": self.parent_epic_id,
|
|
"date_last_activity": self.date_last_activity.isoformat()
|
|
if self.date_last_activity
|
|
else None,
|
|
"created_at": self.created_at.isoformat() if self.created_at else None,
|
|
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
|
|
"metrics": self.metrics or {"card_count": 0},
|
|
}
|
|
|
|
def __repr__(self):
|
|
return f"<Epic {self.name}>"
|