TaskFlow / src /jobs /cleanup_conversations.py
BilalCode's picture
taskflow todo app
310260a
Raw
History Blame Contribute Delete
4.74 kB
"""
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:
# Find conversations to delete
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:
# Delete associated messages
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)
# Delete conversation
session.delete(conversation)
session.commit()
logger.info(
"cleanup_completed",
conversations_deleted=conversation_count,
messages_deleted=message_count,
cutoff_date=cutoff_date.isoformat()
)
else:
# Count messages without deleting
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:
# Find messages with no parent conversation
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()
# Run main cleanup
result = cleanup_old_conversations(dry_run=dry_run)
print(f"Conversations deleted: {result['conversations_deleted']}")
print(f"Messages deleted: {result['messages_deleted']}")
print()
# Run orphaned messages cleanup
if not dry_run:
orphaned_result = cleanup_orphaned_messages()
print(f"Orphaned messages deleted: {orphaned_result['orphaned_messages_deleted']}")
print()
print("Cleanup job completed!")