from typing import List from fastapi import APIRouter, Depends, HTTPException 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, Chat as ChatModel, ChatMember as ChatMemberModel, Message as MessageModel, ChatType, MemberRole from app.schemas.chat import Chat, ChatCreate, DirectChatCreate, ChatUpdate, ChatList, ChatMember, ChatMemberCreate from app.schemas.user import UserPublic from app.websocket.connection_manager import connection_manager router = APIRouter() @router.get("/", response_model=List[ChatList]) def get_user_chats( skip: int = 0, limit: int = 50, current_user: User = Depends(get_current_active_user), db: Session = Depends(get_db) ): """Get all chats for the current user""" # Get user's chat memberships memberships = db.query(ChatMemberModel).filter( ChatMemberModel.user_id == current_user.id ).offset(skip).limit(limit).all() chats = [] for membership in memberships: chat = membership.chat # Get last message last_message = db.query(MessageModel).filter( MessageModel.chat_id == chat.id ).order_by(desc(MessageModel.created_at)).first() # Calculate unread count unread_count = 0 if membership.last_read_message_id: unread_count = db.query(MessageModel).filter( MessageModel.chat_id == chat.id, MessageModel.id > membership.last_read_message_id, MessageModel.sender_id != current_user.id ).count() else: unread_count = db.query(MessageModel).filter( MessageModel.chat_id == chat.id, MessageModel.sender_id != current_user.id ).count() # For direct chats, get the other user's online status is_online = False chat_name = chat.name if chat.chat_type == ChatType.DIRECT: other_member = db.query(ChatMemberModel).filter( ChatMemberModel.chat_id == chat.id, ChatMemberModel.user_id != current_user.id ).first() if other_member: is_online = other_member.user.is_online if not chat_name: chat_name = other_member.user.username chat_data = ChatList( id=chat.id, name=chat_name, chat_type=chat.chat_type, avatar_url=chat.avatar_url, last_message={ "id": last_message.id, "content": last_message.content[:100] if last_message.content else "", "sender_username": last_message.sender.username, "created_at": last_message.created_at.isoformat() } if last_message else None, last_activity=last_message.created_at if last_message else chat.updated_at, unread_count=unread_count, is_online=is_online ) chats.append(chat_data) # Sort by last activity chats.sort(key=lambda x: x.last_activity or x.id, reverse=True) return chats @router.post("/direct", response_model=Chat) def create_direct_chat( chat_data: DirectChatCreate, current_user: User = Depends(get_current_active_user), db: Session = Depends(get_db) ): """Create or get existing direct chat""" other_user = db.query(User).filter(User.id == chat_data.other_user_id).first() if not other_user: raise HTTPException(status_code=404, detail="User not found") if other_user.id == current_user.id: raise HTTPException(status_code=400, detail="Cannot create chat with yourself") # Check if direct chat already exists existing_chat = db.query(ChatModel).join(ChatMemberModel).filter( ChatModel.chat_type == ChatType.DIRECT, ChatMemberModel.user_id.in_([current_user.id, other_user.id]) ).group_by(ChatModel.id).having( db.func.count(ChatMemberModel.user_id) == 2 ).first() if existing_chat: return _get_chat_with_details(existing_chat.id, current_user.id, db) # Create new direct chat chat = ChatModel( chat_type=ChatType.DIRECT, is_active=True ) db.add(chat) db.commit() db.refresh(chat) # Add both users as members for user in [current_user, other_user]: member = ChatMemberModel( chat_id=chat.id, user_id=user.id, role=MemberRole.MEMBER ) db.add(member) db.commit() # Add users to connection manager connection_manager.add_user_to_chat(current_user.id, chat.id) connection_manager.add_user_to_chat(other_user.id, chat.id) return _get_chat_with_details(chat.id, current_user.id, db) @router.post("/group", response_model=Chat) def create_group_chat( chat_data: ChatCreate, current_user: User = Depends(get_current_active_user), db: Session = Depends(get_db) ): """Create a new group chat""" if not chat_data.name: raise HTTPException(status_code=400, detail="Group name is required") # Create group chat chat = ChatModel( name=chat_data.name, description=chat_data.description, chat_type=ChatType.GROUP, avatar_url=chat_data.avatar_url, is_active=True ) db.add(chat) db.commit() db.refresh(chat) # Add creator as owner owner_member = ChatMemberModel( chat_id=chat.id, user_id=current_user.id, role=MemberRole.OWNER ) db.add(owner_member) # Add other members for member_id in chat_data.member_ids: if member_id != current_user.id: # Don't add creator twice user = db.query(User).filter(User.id == member_id).first() if user: member = ChatMemberModel( chat_id=chat.id, user_id=member_id, role=MemberRole.MEMBER ) db.add(member) db.commit() # Add users to connection manager connection_manager.add_user_to_chat(current_user.id, chat.id) for member_id in chat_data.member_ids: connection_manager.add_user_to_chat(member_id, chat.id) return _get_chat_with_details(chat.id, current_user.id, db) @router.get("/{chat_id}", response_model=Chat) def get_chat( chat_id: int, current_user: User = Depends(get_current_active_user), db: Session = Depends(get_db) ): """Get chat details""" # 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") return _get_chat_with_details(chat_id, current_user.id, db) @router.put("/{chat_id}", response_model=Chat) def update_chat( chat_id: int, chat_update: ChatUpdate, current_user: User = Depends(get_current_active_user), db: Session = Depends(get_db) ): """Update chat details (admin/owner only)""" # Verify user is admin/owner of chat membership = db.query(ChatMemberModel).filter( ChatMemberModel.chat_id == chat_id, ChatMemberModel.user_id == current_user.id, ChatMemberModel.role.in_([MemberRole.ADMIN, MemberRole.OWNER]) ).first() if not membership: raise HTTPException(status_code=403, detail="Insufficient permissions") chat = db.query(ChatModel).filter(ChatModel.id == chat_id).first() if not chat: raise HTTPException(status_code=404, detail="Chat not found") update_data = chat_update.dict(exclude_unset=True) for field, value in update_data.items(): setattr(chat, field, value) db.commit() db.refresh(chat) return _get_chat_with_details(chat_id, current_user.id, db) @router.post("/{chat_id}/members", response_model=ChatMember) def add_chat_member( chat_id: int, member_data: ChatMemberCreate, current_user: User = Depends(get_current_active_user), db: Session = Depends(get_db) ): """Add member to chat (admin/owner only)""" # Verify permissions membership = db.query(ChatMemberModel).filter( ChatMemberModel.chat_id == chat_id, ChatMemberModel.user_id == current_user.id, ChatMemberModel.role.in_([MemberRole.ADMIN, MemberRole.OWNER]) ).first() if not membership: raise HTTPException(status_code=403, detail="Insufficient permissions") # Check if user is already a member existing_member = db.query(ChatMemberModel).filter( ChatMemberModel.chat_id == chat_id, ChatMemberModel.user_id == member_data.user_id ).first() if existing_member: raise HTTPException(status_code=400, detail="User is already a member") # Verify user exists user = db.query(User).filter(User.id == member_data.user_id).first() if not user: raise HTTPException(status_code=404, detail="User not found") # Add member new_member = ChatMemberModel( chat_id=chat_id, user_id=member_data.user_id, role=member_data.role, nickname=member_data.nickname ) db.add(new_member) db.commit() db.refresh(new_member) # Add to connection manager connection_manager.add_user_to_chat(member_data.user_id, chat_id) return ChatMember( id=new_member.id, chat_id=new_member.chat_id, user_id=new_member.user_id, role=new_member.role, nickname=new_member.nickname, is_muted=new_member.is_muted, is_banned=new_member.is_banned, joined_at=new_member.joined_at, last_read_message_id=new_member.last_read_message_id, user=UserPublic.from_orm(user) ) @router.delete("/{chat_id}/members/{user_id}") def remove_chat_member( chat_id: int, user_id: int, current_user: User = Depends(get_current_active_user), db: Session = Depends(get_db) ): """Remove member from chat""" # Users can remove themselves, or admins/owners can remove others if user_id != current_user.id: membership = db.query(ChatMemberModel).filter( ChatMemberModel.chat_id == chat_id, ChatMemberModel.user_id == current_user.id, ChatMemberModel.role.in_([MemberRole.ADMIN, MemberRole.OWNER]) ).first() if not membership: raise HTTPException(status_code=403, detail="Insufficient permissions") # Find and remove member member = db.query(ChatMemberModel).filter( ChatMemberModel.chat_id == chat_id, ChatMemberModel.user_id == user_id ).first() if not member: raise HTTPException(status_code=404, detail="Member not found") db.delete(member) db.commit() # Remove from connection manager connection_manager.remove_user_from_chat(user_id, chat_id) return {"message": "Member removed successfully"} def _get_chat_with_details(chat_id: int, current_user_id: int, db: Session) -> Chat: """Helper function to get chat with all details""" chat = db.query(ChatModel).filter(ChatModel.id == chat_id).first() # Get members members = db.query(ChatMemberModel).filter(ChatMemberModel.chat_id == chat_id).all() chat_members = [] for member in members: user_public = UserPublic.from_orm(member.user) chat_member = ChatMember( id=member.id, chat_id=member.chat_id, user_id=member.user_id, role=member.role, nickname=member.nickname, is_muted=member.is_muted, is_banned=member.is_banned, joined_at=member.joined_at, last_read_message_id=member.last_read_message_id, user=user_public ) chat_members.append(chat_member) # Get last message last_message = db.query(MessageModel).filter( MessageModel.chat_id == chat_id ).order_by(desc(MessageModel.created_at)).first() # Calculate unread count current_member = next((m for m in members if m.user_id == current_user_id), None) unread_count = 0 if current_member and current_member.last_read_message_id: unread_count = db.query(MessageModel).filter( MessageModel.chat_id == chat_id, MessageModel.id > current_member.last_read_message_id, MessageModel.sender_id != current_user_id ).count() else: unread_count = db.query(MessageModel).filter( MessageModel.chat_id == chat_id, MessageModel.sender_id != current_user_id ).count() return Chat( id=chat.id, name=chat.name, description=chat.description, chat_type=chat.chat_type, avatar_url=chat.avatar_url, is_active=chat.is_active, created_at=chat.created_at, updated_at=chat.updated_at, members=chat_members, last_message={ "id": last_message.id, "content": last_message.content, "sender_username": last_message.sender.username, "created_at": last_message.created_at.isoformat() } if last_message else None, unread_count=unread_count )