feat: Improve file display, timezone handling, and LOT management
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>
This commit is contained in:
134
scripts/migrate_orphan_files.py
Normal file
134
scripts/migrate_orphan_files.py
Normal file
@@ -0,0 +1,134 @@
|
||||
"""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.")
|
||||
Reference in New Issue
Block a user