| """ |
| Conversation Cleanup Job |
| |
| Implements 90-day retention policy for conversations and messages. |
| Permanently deletes soft-deleted conversations older than 90 days. |
| """ |
|
|
| from sqlmodel import Session, select |
| from datetime import datetime, timedelta |
| from ..database import engine |
| from ..models.conversation import Conversation |
| from ..models.message import Message |
| from ..utils.logging import StructuredLogger |
|
|
| logger = StructuredLogger("cleanup") |
|
|
|
|
| def cleanup_old_conversations(dry_run: bool = False) -> dict: |
| """ |
| Delete conversations and messages older than 90 days. |
| |
| This function implements the 90-day retention policy by: |
| 1. Finding conversations soft-deleted more than 90 days ago |
| 2. Deleting associated messages |
| 3. Permanently deleting the conversations |
| |
| Args: |
| dry_run: If True, only count records without deleting |
| |
| Returns: |
| Dict with cleanup statistics |
| """ |
| cutoff_date = datetime.utcnow() - timedelta(days=90) |
|
|
| with Session(engine) as session: |
| |
| statement = select(Conversation).where( |
| Conversation.deleted_at.is_not(None), |
| Conversation.deleted_at < cutoff_date |
| ) |
| conversations_to_delete = session.exec(statement).all() |
|
|
| conversation_count = len(conversations_to_delete) |
| message_count = 0 |
|
|
| if not dry_run: |
| for conversation in conversations_to_delete: |
| |
| message_statement = select(Message).where( |
| Message.conversation_id == conversation.id |
| ) |
| messages = session.exec(message_statement).all() |
| message_count += len(messages) |
|
|
| for message in messages: |
| session.delete(message) |
|
|
| |
| session.delete(conversation) |
|
|
| session.commit() |
|
|
| logger.info( |
| "cleanup_completed", |
| conversations_deleted=conversation_count, |
| messages_deleted=message_count, |
| cutoff_date=cutoff_date.isoformat() |
| ) |
| else: |
| |
| for conversation in conversations_to_delete: |
| message_statement = select(Message).where( |
| Message.conversation_id == conversation.id |
| ) |
| messages = session.exec(message_statement).all() |
| message_count += len(messages) |
|
|
| logger.info( |
| "cleanup_dry_run", |
| conversations_to_delete=conversation_count, |
| messages_to_delete=message_count, |
| cutoff_date=cutoff_date.isoformat() |
| ) |
|
|
| return { |
| "conversations_deleted": conversation_count, |
| "messages_deleted": message_count, |
| "cutoff_date": cutoff_date.isoformat(), |
| "dry_run": dry_run |
| } |
|
|
|
|
| def cleanup_orphaned_messages() -> dict: |
| """ |
| Delete messages that belong to deleted conversations. |
| |
| This is a safety cleanup for any orphaned messages. |
| |
| Returns: |
| Dict with cleanup statistics |
| """ |
| with Session(engine) as session: |
| |
| statement = select(Message).where( |
| ~Message.conversation_id.in_( |
| select(Conversation.id) |
| ) |
| ) |
| orphaned_messages = session.exec(statement).all() |
|
|
| count = len(orphaned_messages) |
|
|
| for message in orphaned_messages: |
| session.delete(message) |
|
|
| session.commit() |
|
|
| logger.info( |
| "orphaned_messages_cleanup", |
| messages_deleted=count |
| ) |
|
|
| return { |
| "orphaned_messages_deleted": count |
| } |
|
|
|
|
| if __name__ == "__main__": |
| """ |
| Run cleanup job from command line. |
| |
| Usage: |
| python -m backend.src.jobs.cleanup_conversations |
| python -m backend.src.jobs.cleanup_conversations --dry-run |
| """ |
| import sys |
|
|
| dry_run = "--dry-run" in sys.argv |
|
|
| print("Starting conversation cleanup job...") |
| print(f"Dry run: {dry_run}") |
| print(f"Cutoff date: {(datetime.utcnow() - timedelta(days=90)).isoformat()}") |
| print() |
|
|
| |
| result = cleanup_old_conversations(dry_run=dry_run) |
| print(f"Conversations deleted: {result['conversations_deleted']}") |
| print(f"Messages deleted: {result['messages_deleted']}") |
| print() |
|
|
| |
| if not dry_run: |
| orphaned_result = cleanup_orphaned_messages() |
| print(f"Orphaned messages deleted: {orphaned_result['orphaned_messages_deleted']}") |
| print() |
|
|
| print("Cleanup job completed!") |
|
|