from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.orm import Session from sqlalchemy import desc from app.db.session import get_db from app.core.deps import get_current_active_user from app.models import User, ChatMember as ChatMemberModel, Message as MessageModel from app.schemas.message import Message, MessageUpdate, MessageList, MediaFile, MessageMention router = APIRouter() @router.get("/{chat_id}", response_model=MessageList) def get_chat_messages( chat_id: int, page: int = Query(1, ge=1), limit: int = Query(50, ge=1, le=100), current_user: User = Depends(get_current_active_user), db: Session = Depends(get_db) ): """Get messages for a chat with pagination""" # Verify user is member of chat membership = db.query(ChatMemberModel).filter( ChatMemberModel.chat_id == chat_id, ChatMemberModel.user_id == current_user.id ).first() if not membership: raise HTTPException(status_code=404, detail="Chat not found") # Calculate offset offset = (page - 1) * limit # Get total count total = db.query(MessageModel).filter( MessageModel.chat_id == chat_id, MessageModel.is_deleted.is_(False) ).count() # Get messages messages_query = db.query(MessageModel).filter( MessageModel.chat_id == chat_id, MessageModel.is_deleted.is_(False) ).order_by(desc(MessageModel.created_at)).offset(offset).limit(limit) db_messages = messages_query.all() # Convert to response format messages = [] for msg in db_messages: # Get media files media_files = [] for media in msg.media_files: media_file = MediaFile( id=media.id, filename=media.filename, original_filename=media.original_filename, file_size=media.file_size, mime_type=media.mime_type, media_type=media.media_type.value, width=media.width, height=media.height, duration=media.duration, thumbnail_url=f"/api/media/{media.id}/thumbnail" if media.thumbnail_path else None ) media_files.append(media_file) # Get mentions mentions = [] for mention in msg.mentions: mention_data = MessageMention( id=mention.mentioned_user.id, username=mention.mentioned_user.username, full_name=mention.mentioned_user.full_name ) mentions.append(mention_data) # Get reply-to message if exists reply_to = None if msg.reply_to: reply_to = Message( id=msg.reply_to.id, chat_id=msg.reply_to.chat_id, sender_id=msg.reply_to.sender_id, content=msg.reply_to.content, content_type=msg.reply_to.content_type, status=msg.reply_to.status, is_edited=msg.reply_to.is_edited, is_deleted=msg.reply_to.is_deleted, edited_at=msg.reply_to.edited_at, created_at=msg.reply_to.created_at, sender_username=msg.reply_to.sender.username, sender_avatar=msg.reply_to.sender.avatar_url, media_files=[], mentions=[], reply_to=None ) message = Message( id=msg.id, chat_id=msg.chat_id, sender_id=msg.sender_id, content=msg.content, content_type=msg.content_type, reply_to_id=msg.reply_to_id, status=msg.status, is_edited=msg.is_edited, is_deleted=msg.is_deleted, edited_at=msg.edited_at, created_at=msg.created_at, sender_username=msg.sender.username, sender_avatar=msg.sender.avatar_url, media_files=media_files, mentions=mentions, reply_to=reply_to ) messages.append(message) # Reverse to show oldest first messages.reverse() return MessageList( messages=messages, total=total, page=page, has_more=offset + limit < total ) @router.put("/{message_id}", response_model=Message) def update_message( message_id: int, message_update: MessageUpdate, current_user: User = Depends(get_current_active_user), db: Session = Depends(get_db) ): """Update/edit a message (sender only)""" message = db.query(MessageModel).filter(MessageModel.id == message_id).first() if not message: raise HTTPException(status_code=404, detail="Message not found") if message.sender_id != current_user.id: raise HTTPException(status_code=403, detail="Can only edit your own messages") if message.is_deleted: raise HTTPException(status_code=400, detail="Cannot edit deleted message") # Update message update_data = message_update.dict(exclude_unset=True) for field, value in update_data.items(): setattr(message, field, value) message.is_edited = True from datetime import datetime message.edited_at = datetime.utcnow() db.commit() db.refresh(message) # Return updated message (simplified response) return Message( id=message.id, chat_id=message.chat_id, sender_id=message.sender_id, content=message.content, content_type=message.content_type, reply_to_id=message.reply_to_id, status=message.status, is_edited=message.is_edited, is_deleted=message.is_deleted, edited_at=message.edited_at, created_at=message.created_at, sender_username=message.sender.username, sender_avatar=message.sender.avatar_url, media_files=[], mentions=[], reply_to=None ) @router.delete("/{message_id}") def delete_message( message_id: int, current_user: User = Depends(get_current_active_user), db: Session = Depends(get_db) ): """Delete a message (sender only or admin/owner)""" message = db.query(MessageModel).filter(MessageModel.id == message_id).first() if not message: raise HTTPException(status_code=404, detail="Message not found") # Check if user can delete (sender or admin/owner of chat) can_delete = message.sender_id == current_user.id if not can_delete: # Check if user is admin/owner of the chat from app.models.chat_member import MemberRole membership = db.query(ChatMemberModel).filter( ChatMemberModel.chat_id == message.chat_id, ChatMemberModel.user_id == current_user.id, ChatMemberModel.role.in_([MemberRole.ADMIN, MemberRole.OWNER]) ).first() can_delete = membership is not None if not can_delete: raise HTTPException(status_code=403, detail="Insufficient permissions") # Soft delete message.is_deleted = True message.content = "[Deleted]" db.commit() return {"message": "Message deleted successfully"} @router.post("/{message_id}/read") def mark_message_read( message_id: int, current_user: User = Depends(get_current_active_user), db: Session = Depends(get_db) ): """Mark message as read""" message = db.query(MessageModel).filter(MessageModel.id == message_id).first() if not message: raise HTTPException(status_code=404, detail="Message not found") # Update user's last read message membership = db.query(ChatMemberModel).filter( ChatMemberModel.chat_id == message.chat_id, ChatMemberModel.user_id == current_user.id ).first() if membership: if not membership.last_read_message_id or membership.last_read_message_id < message.id: membership.last_read_message_id = message.id db.commit() return {"message": "Message marked as read"}