swiftops-backend / src /app /models /ticket_comment.py
kamau1's picture
Fix module import paths: remove incorrect 'src.' prefix from imports
cb949a1
"""
Ticket Comment Model - Collaboration & Communication
Enables team collaboration on tickets with threaded discussions.
Supports internal comments (team only) and external comments (visible to client).
"""
from sqlalchemy import Column, String, ForeignKey, Boolean, TIMESTAMP, Text, CheckConstraint
from sqlalchemy.dialects.postgresql import UUID, JSONB, ARRAY
from sqlalchemy.orm import relationship
from datetime import datetime
import uuid
from app.core.database import Base
class TicketComment(Base):
"""
Ticket Comment Model
Threaded comments on tickets. Supports internal (team) and external (client-visible)
comments with mentions and attachments.
Links to:
- tickets (required) - which ticket this comment belongs to
- users (commented by, edited by) - who created/edited comment
- ticket_comments (parent) - for threaded discussions
- documents (attachments) - optional file attachments
"""
__tablename__ = "ticket_comments"
# Primary Key
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
# Foreign Keys
ticket_id = Column(
UUID(as_uuid=True),
ForeignKey("tickets.id", ondelete="CASCADE"),
nullable=False,
index=True
)
user_id = Column(
UUID(as_uuid=True),
ForeignKey("users.id", ondelete="SET NULL"),
nullable=True, # Nullable: user may be deleted
index=True
)
# Comment Content
comment_text = Column(Text, nullable=False)
# Comment Type & Visibility
is_internal = Column(Boolean, default=True, nullable=False) # TRUE = team only, FALSE = visible to client
comment_type = Column(String(50), default='note', nullable=False) # 'note', 'issue', 'resolution', 'question', 'update'
# Threading (for replies)
parent_comment_id = Column(
UUID(as_uuid=True),
ForeignKey("ticket_comments.id", ondelete="CASCADE"),
nullable=True,
index=True
)
# Mentions & notifications
mentioned_user_ids = Column(ARRAY(UUID(as_uuid=True)), nullable=True) # Array of user IDs mentioned in comment (e.g., @john)
# Attachments (optional - links to documents)
attachment_document_ids = Column(ARRAY(UUID(as_uuid=True)), nullable=True) # Array of document IDs attached to comment
# Edit Tracking
is_edited = Column(Boolean, default=False, nullable=False)
edited_at = Column(TIMESTAMP(timezone=True), nullable=True)
edited_by_user_id = Column(
UUID(as_uuid=True),
ForeignKey("users.id", ondelete="SET NULL"),
nullable=True
)
# Metadata
additional_metadata = Column(
JSONB,
nullable=False,
default={},
server_default="{}"
)
# Timestamps
created_at = Column(
TIMESTAMP(timezone=True),
nullable=False,
default=datetime.utcnow,
server_default="timezone('utc'::text, now())"
)
updated_at = Column(
TIMESTAMP(timezone=True),
nullable=False,
default=datetime.utcnow,
onupdate=datetime.utcnow,
server_default="timezone('utc'::text, now())"
)
deleted_at = Column(TIMESTAMP(timezone=True), nullable=True)
# Relationships
ticket = relationship("Ticket", back_populates="comments")
user = relationship("User", foreign_keys=[user_id])
edited_by_user = relationship("User", foreign_keys=[edited_by_user_id])
parent_comment = relationship("TicketComment", remote_side=[id], backref="replies")
# Constraints
__table_args__ = (
CheckConstraint("LENGTH(TRIM(comment_text)) > 0", name='chk_comment_not_empty'),
)
def __repr__(self):
return f"<TicketComment(id={self.id}, ticket_id={self.ticket_id}, is_internal={self.is_internal})>"
def to_dict(self):
"""Convert comment to dictionary"""
return {
"id": str(self.id),
"ticket_id": str(self.ticket_id),
"user_id": str(self.user_id) if self.user_id else None,
"comment_text": self.comment_text,
"is_internal": self.is_internal,
"comment_type": self.comment_type,
"parent_comment_id": str(self.parent_comment_id) if self.parent_comment_id else None,
"mentioned_user_ids": [str(uid) for uid in self.mentioned_user_ids] if self.mentioned_user_ids else [],
"attachment_document_ids": [str(did) for did in self.attachment_document_ids] if self.attachment_document_ids else [],
"is_edited": self.is_edited,
"edited_at": self.edited_at.isoformat() if self.edited_at else None,
"edited_by_user_id": str(self.edited_by_user_id) if self.edited_by_user_id else None,
"additional_metadata": self.additional_metadata,
"created_at": self.created_at.isoformat() if self.created_at else None,
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
}