Changes: - Fix datetime serialization with UTC 'Z' suffix for correct timezone display - Add PDF upload support with extension fallback for MIME detection - Fix LOT add/remove by creating new list for SQLAlchemy JSON change detection - Add file message components (FileMessage, ImageLightbox, UploadPreview) - Add multi-file upload support with progress tracking - Link uploaded files to chat messages via message_id - Include file attachments in AI report generation - Update specs for file-storage, realtime-messaging, and ai-report-generation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
135 lines
4.2 KiB
Python
135 lines
4.2 KiB
Python
"""Migration script to create messages for existing files without message_id
|
|
|
|
This script:
|
|
1. Finds all files in tr_room_files that have no message_id
|
|
2. Creates an associated message (image_ref or file_ref) for each
|
|
3. Updates the file record with the new message_id
|
|
|
|
Run from project root:
|
|
python scripts/migrate_orphan_files.py
|
|
"""
|
|
import sys
|
|
import os
|
|
|
|
# Add project root to path
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
from datetime import datetime
|
|
from sqlalchemy import create_engine
|
|
from sqlalchemy.orm import sessionmaker
|
|
|
|
from app.core.config import get_settings
|
|
from app.modules.file_storage.models import RoomFile
|
|
from app.modules.realtime.models import Message, MessageType
|
|
from app.modules.file_storage.services.minio_service import generate_presigned_url
|
|
|
|
settings = get_settings()
|
|
|
|
|
|
def get_db_session():
|
|
"""Create database session"""
|
|
engine = create_engine(settings.DATABASE_URL)
|
|
Session = sessionmaker(bind=engine)
|
|
return Session()
|
|
|
|
|
|
def migrate_orphan_files():
|
|
"""Migrate orphan files by creating associated messages"""
|
|
db = get_db_session()
|
|
|
|
try:
|
|
# Find files without message_id
|
|
orphan_files = db.query(RoomFile).filter(
|
|
RoomFile.message_id.is_(None),
|
|
RoomFile.deleted_at.is_(None)
|
|
).all()
|
|
|
|
if not orphan_files:
|
|
print("No orphan files found. Nothing to migrate.")
|
|
return
|
|
|
|
print(f"Found {len(orphan_files)} orphan files to migrate.")
|
|
|
|
migrated = 0
|
|
failed = 0
|
|
|
|
for file in orphan_files:
|
|
try:
|
|
# Generate presigned URL for file
|
|
download_url = generate_presigned_url(
|
|
bucket=file.minio_bucket,
|
|
object_path=file.minio_object_path,
|
|
expiry_seconds=3600
|
|
)
|
|
|
|
# Determine message type
|
|
if file.file_type == "image":
|
|
msg_type = MessageType.IMAGE_REF
|
|
content = f"[Image] {file.filename}"
|
|
else:
|
|
msg_type = MessageType.FILE_REF
|
|
content = f"[File] {file.filename}"
|
|
|
|
# Get next sequence number for the room
|
|
max_seq = db.query(Message.sequence_number).filter(
|
|
Message.room_id == file.room_id
|
|
).order_by(Message.sequence_number.desc()).first()
|
|
next_seq = (max_seq[0] + 1) if max_seq else 1
|
|
|
|
# Create metadata
|
|
metadata = {
|
|
"file_id": file.file_id,
|
|
"file_url": download_url,
|
|
"filename": file.filename,
|
|
"file_type": file.file_type,
|
|
"mime_type": file.mime_type,
|
|
"file_size": file.file_size,
|
|
}
|
|
|
|
# Add thumbnail_url for images
|
|
if file.file_type == "image":
|
|
metadata["thumbnail_url"] = download_url
|
|
|
|
# Create message
|
|
message = Message(
|
|
room_id=file.room_id,
|
|
sender_id=file.uploader_id,
|
|
content=content,
|
|
message_type=msg_type,
|
|
message_metadata=metadata,
|
|
created_at=file.uploaded_at, # Use original upload time
|
|
sequence_number=next_seq,
|
|
)
|
|
|
|
db.add(message)
|
|
db.flush() # Get the message_id
|
|
|
|
# Update file with message_id
|
|
file.message_id = message.message_id
|
|
|
|
migrated += 1
|
|
print(f" Migrated: {file.filename} -> message {message.message_id}")
|
|
|
|
except Exception as e:
|
|
failed += 1
|
|
print(f" Failed: {file.filename} - {e}")
|
|
|
|
# Commit all changes
|
|
db.commit()
|
|
print(f"\nMigration complete: {migrated} migrated, {failed} failed")
|
|
|
|
except Exception as e:
|
|
db.rollback()
|
|
print(f"Migration failed: {e}")
|
|
raise
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
print("Starting orphan files migration...")
|
|
print("=" * 50)
|
|
migrate_orphan_files()
|
|
print("=" * 50)
|
|
print("Done.")
|