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:
@@ -79,6 +79,7 @@ class IncidentRoom(Base):
|
||||
# Relationships
|
||||
members = relationship("RoomMember", back_populates="room", cascade="all, delete-orphan")
|
||||
files = relationship("RoomFile", back_populates="room", cascade="all, delete-orphan")
|
||||
reports = relationship("GeneratedReport", back_populates="room", cascade="all, delete-orphan")
|
||||
|
||||
# Indexes for common queries
|
||||
__table_args__ = (
|
||||
|
||||
@@ -193,7 +193,7 @@ async def permanent_delete_room(
|
||||
"type": "system",
|
||||
"event": "room_deleted",
|
||||
"room_id": room_id,
|
||||
"timestamp": datetime.utcnow().isoformat()
|
||||
"timestamp": datetime.utcnow().isoformat() + "Z"
|
||||
})
|
||||
|
||||
success, error = room_service.permanent_delete_room(db, room_id)
|
||||
@@ -246,7 +246,7 @@ async def join_room(
|
||||
detail={
|
||||
"message": "Already a member of this room",
|
||||
"current_role": existing.role.value,
|
||||
"added_at": existing.added_at.isoformat()
|
||||
"added_at": existing.added_at.isoformat() + "Z"
|
||||
}
|
||||
)
|
||||
|
||||
@@ -505,12 +505,12 @@ async def add_lot(
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Room not found")
|
||||
|
||||
# Get current lots or initialize empty list
|
||||
current_lots = room.lots or []
|
||||
current_lots = list(room.lots or []) # Create a new list to ensure change detection
|
||||
|
||||
# Prevent duplicates
|
||||
if request.lot not in current_lots:
|
||||
current_lots.append(request.lot)
|
||||
room.lots = current_lots
|
||||
room.lots = current_lots # Assign new list triggers SQLAlchemy change detection
|
||||
room.last_updated_at = datetime.utcnow()
|
||||
db.commit()
|
||||
db.refresh(room)
|
||||
@@ -532,11 +532,11 @@ async def remove_lot(
|
||||
if not room:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Room not found")
|
||||
|
||||
current_lots = room.lots or []
|
||||
current_lots = list(room.lots or []) # Create a new list to ensure change detection
|
||||
|
||||
if lot in current_lots:
|
||||
current_lots.remove(lot)
|
||||
room.lots = current_lots
|
||||
room.lots = current_lots # Assign new list triggers SQLAlchemy change detection
|
||||
room.last_updated_at = datetime.utcnow()
|
||||
db.commit()
|
||||
db.refresh(room)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Request and response models for API endpoints
|
||||
"""
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic import BaseModel, Field, ConfigDict, field_serializer
|
||||
from typing import Optional, List
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
@@ -98,8 +98,14 @@ class MemberResponse(BaseModel):
|
||||
added_at: datetime
|
||||
removed_at: Optional[datetime] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
@field_serializer("added_at", "removed_at")
|
||||
def serialize_datetime(self, dt: Optional[datetime]) -> Optional[str]:
|
||||
"""Serialize datetime with 'Z' suffix to indicate UTC"""
|
||||
if dt is None:
|
||||
return None
|
||||
return dt.isoformat() + "Z"
|
||||
|
||||
|
||||
class RoomResponse(BaseModel):
|
||||
@@ -127,8 +133,17 @@ class RoomResponse(BaseModel):
|
||||
is_member: bool = False
|
||||
is_admin_view: bool = False
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
@field_serializer(
|
||||
"created_at", "resolved_at", "archived_at",
|
||||
"last_activity_at", "last_updated_at", "ownership_transferred_at"
|
||||
)
|
||||
def serialize_datetime(self, dt: Optional[datetime]) -> Optional[str]:
|
||||
"""Serialize datetime with 'Z' suffix to indicate UTC"""
|
||||
if dt is None:
|
||||
return None
|
||||
return dt.isoformat() + "Z"
|
||||
|
||||
|
||||
class RoomListResponse(BaseModel):
|
||||
|
||||
@@ -467,7 +467,11 @@ class RoomService:
|
||||
f"Failed to delete report file: {report.docx_storage_path}"
|
||||
)
|
||||
|
||||
# Step 3: Delete room from database (CASCADE handles related tables)
|
||||
# Step 3: Delete reports from database (before room delete due to relationship handling)
|
||||
for report in reports:
|
||||
db.delete(report)
|
||||
|
||||
# Step 4: Delete room from database (CASCADE handles other related tables)
|
||||
db.delete(room)
|
||||
db.commit()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user