"""Service for managing file attachments to entities""" from io import BytesIO from typing import List, Optional from flask import current_app from app.models import Card, Comment, FileAttachment from app.services.storage.file_service import FileService class AttachmentService: """Service for managing file attachments to Cards, Comments, etc.""" def __init__(self, file_service: Optional[FileService] = None): """ Initialize AttachmentService with dependency injection Args: file_service: FileService instance (defaults to new instance with dependency injection) """ self.file_service = file_service or FileService() def attach_file_to_card( self, card_id: int, file_data: BytesIO, filename: str, content_type: str, file_size: int, uploaded_by: int, ) -> FileAttachment: """ Attach a file to a card Args: card_id: ID of the card file_data: File data as BytesIO filename: Original filename content_type: MIME type file_size: File size in bytes uploaded_by: User ID who uploaded the file Returns: FileAttachment: Created attachment record """ # Verify card exists card = Card.query.get(card_id) if not card: raise ValueError(f"Card with ID {card_id} not found") # Upload file return self.file_service.upload_file( file_data=file_data, filename=filename, content_type=content_type, file_size=file_size, uploaded_by=uploaded_by, attachable_type="Card", attachable_id=card_id, ) def attach_file_to_comment( self, comment_id: int, file_data: BytesIO, filename: str, content_type: str, file_size: int, uploaded_by: int, ) -> FileAttachment: """ Attach a file to a comment Args: comment_id: ID of the comment file_data: File data as BytesIO filename: Original filename content_type: MIME type file_size: File size in bytes uploaded_by: User ID who uploaded the file Returns: FileAttachment: Created attachment record """ # Verify comment exists comment = Comment.query.get(comment_id) if not comment: raise ValueError(f"Comment with ID {comment_id} not found") # Upload file return self.file_service.upload_file( file_data=file_data, filename=filename, content_type=content_type, file_size=file_size, uploaded_by=uploaded_by, attachable_type="Comment", attachable_id=comment_id, ) def attach_file_to_entity( self, entity_type: str, entity_id: int, file_data: BytesIO, filename: str, content_type: str, file_size: int, uploaded_by: int, ) -> FileAttachment: """ Attach a file to any entity type Args: entity_type: Type of entity (Card, Comment, etc.) entity_id: ID of the entity file_data: File data as BytesIO filename: Original filename content_type: MIME type file_size: File size in bytes uploaded_by: User ID who uploaded the file Returns: FileAttachment: Created attachment record """ # Validate entity type valid_types = ["Card", "Comment", "Epic", "Board"] if entity_type not in valid_types: raise ValueError( f"Invalid entity type: {entity_type}. Must be one of {valid_types}" ) # Upload file return self.file_service.upload_file( file_data=file_data, filename=filename, content_type=content_type, file_size=file_size, uploaded_by=uploaded_by, attachable_type=entity_type, attachable_id=entity_id, ) def get_card_attachments(self, card_id: int) -> List[FileAttachment]: """ Get all attachments for a card Args: card_id: ID of the card Returns: list: List of FileAttachment objects """ return self.file_service.get_files_for_entity("Card", card_id) def get_comment_attachments(self, comment_id: int) -> List[FileAttachment]: """ Get all attachments for a comment Args: comment_id: ID of the comment Returns: list: List of FileAttachment objects """ return self.file_service.get_files_for_entity("Comment", comment_id) def get_entity_attachments( self, entity_type: str, entity_id: int ) -> List[FileAttachment]: """ Get all attachments for any entity type Args: entity_type: Type of entity (Card, Comment, etc.) entity_id: ID of the entity Returns: list: List of FileAttachment objects """ return self.file_service.get_files_for_entity(entity_type, entity_id) def get_attachment_with_url(self, attachment_id: int) -> dict: """ Get attachment with download URL Args: attachment_id: ID of the attachment Returns: dict: Attachment information with URLs """ return self.file_service.get_file_info(attachment_id) def delete_attachment(self, attachment_id: int) -> bool: """ Delete an attachment Args: attachment_id: ID of the attachment Returns: bool: True if successful """ return self.file_service.delete_file(attachment_id) def delete_entity_attachments(self, entity_type: str, entity_id: int) -> int: """ Delete all attachments for an entity (cascade delete) Args: entity_type: Type of entity (Card, Comment, etc.) entity_id: ID of the entity Returns: int: Number of attachments deleted """ attachments = self.get_entity_attachments(entity_type, entity_id) deleted_count = 0 for attachment in attachments: if self.delete_attachment(attachment.id): deleted_count += 1 current_app.logger.info( f"Deleted {deleted_count} attachments for {entity_type} {entity_id}" ) return deleted_count