from fastapi import APIRouter, Depends, HTTPException, status, BackgroundTasks from sqlmodel import Session, select from typing import List import os from app.database import get_session from app.models import Message, Channel, User, FileAttachment from app.schemas import MessageCreate, MessageResponse from app.auth import get_current_user from app.websocket import manager router = APIRouter(prefix="/messages", tags=["Messages"]) def user_has_channel_access(user: User, channel_id: int, session: Session) -> bool: """Check if user has access to a channel""" channel = session.get(Channel, channel_id) if not channel: return False user_dept_ids = [dept.id for dept in user.departments] return channel.department_id in user_dept_ids @router.post("/", response_model=MessageResponse, status_code=status.HTTP_201_CREATED) async def create_message( message_data: MessageCreate, background_tasks: BackgroundTasks, session: Session = Depends(get_session), current_user: User = Depends(get_current_user) ): """Create a new message in a channel""" # Check if channel exists channel = session.get(Channel, message_data.channel_id) if not channel: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Channel not found" ) # Check if user has access to this channel if not user_has_channel_access(current_user, message_data.channel_id, session): raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="You don't have access to this channel" ) new_message = Message( content=message_data.content, sender_id=current_user.id, channel_id=message_data.channel_id, snippet_id=message_data.snippet_id, reply_to_id=message_data.reply_to_id ) session.add(new_message) session.commit() session.refresh(new_message) # Build response dict manually to avoid issues with relationships reply_to_data = None if new_message.reply_to_id: reply_msg = session.get(Message, new_message.reply_to_id) if reply_msg: reply_sender = session.get(User, reply_msg.sender_id) reply_to_data = { "id": reply_msg.id, "content": reply_msg.content, "sender_username": reply_sender.username if reply_sender else "Unknown" } response_data = { "id": new_message.id, "content": new_message.content, "channel_id": new_message.channel_id, "sender_id": new_message.sender_id, "sender_username": current_user.username, "sender_full_name": current_user.full_name, "sender_profile_picture": current_user.profile_picture, "created_at": new_message.created_at.isoformat(), "snippet_id": new_message.snippet_id, "reply_to_id": new_message.reply_to_id, "reply_to": reply_to_data, "snippet": None, "attachments": [], "is_deleted": False } # Broadcast to all connected clients in this channel await manager.broadcast_to_channel( { "type": "message", "message": response_data }, message_data.channel_id ) # Return proper response response = MessageResponse.model_validate(new_message) response.sender_username = current_user.username response.sender_full_name = current_user.full_name response.sender_profile_picture = current_user.profile_picture return response @router.get("/channel/{channel_id}", response_model=List[MessageResponse]) def get_channel_messages( channel_id: int, limit: int = 50, offset: int = 0, session: Session = Depends(get_session), current_user: User = Depends(get_current_user) ): """Get messages from a channel""" # Check if user has access to this channel if not user_has_channel_access(current_user, channel_id, session): raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="You don't have access to this channel" ) statement = ( select(Message) .where(Message.channel_id == channel_id) .order_by(Message.created_at.desc()) .offset(offset) .limit(limit) ) messages = session.exec(statement).all() # Add sender usernames and reply_to info responses = [] for msg in messages: msg_response = MessageResponse.model_validate(msg) sender = session.get(User, msg.sender_id) msg_response.sender_username = sender.username if sender else "Unknown" msg_response.sender_full_name = sender.full_name if sender else None msg_response.sender_profile_picture = sender.profile_picture if sender else None # Add reply_to info if exists if msg.reply_to_id: reply_msg = session.get(Message, msg.reply_to_id) if reply_msg: reply_sender = session.get(User, reply_msg.sender_id) msg_response.reply_to = { "id": reply_msg.id, "content": reply_msg.content, "sender_username": reply_sender.username if reply_sender else "Unknown" } responses.append(msg_response) # Reverse to show oldest first return list(reversed(responses)) @router.get("/{message_id}", response_model=MessageResponse) def get_message( message_id: int, session: Session = Depends(get_session), current_user: User = Depends(get_current_user) ): """Get a specific message""" message = session.get(Message, message_id) if not message: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Message not found" ) # Check if user has access to this message's channel if not user_has_channel_access(current_user, message.channel_id, session): raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="You don't have access to this message" ) response = MessageResponse.model_validate(message) sender = session.get(User, message.sender_id) response.sender_username = sender.username if sender else "Unknown" response.sender_full_name = sender.full_name if sender else None response.sender_profile_picture = sender.profile_picture if sender else None return response @router.delete("/{message_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_message( message_id: int, session: Session = Depends(get_session), current_user: User = Depends(get_current_user) ): """Delete a message and its attachments""" message = session.get(Message, message_id) if not message: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Message not found" ) # Only sender can delete their own messages if message.sender_id != current_user.id: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="You can only delete your own messages" ) # Check channel access if not user_has_channel_access(current_user, message.channel_id, session): raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="You don't have access to this channel" ) channel_id = message.channel_id # Delete all file attachments statement = select(FileAttachment).where(FileAttachment.message_id == message_id) attachments = session.exec(statement).all() for attachment in attachments: # Delete physical file if os.path.exists(attachment.file_path): try: os.remove(attachment.file_path) except Exception as e: print(f"Error deleting file {attachment.file_path}: {e}") # Delete database record session.delete(attachment) # Mark message as deleted instead of removing it message.is_deleted = True message.content = "Diese Nachricht wurde gelöscht" session.add(message) session.commit() # Broadcast deletion to all clients await manager.broadcast_to_channel( { "type": "message_deleted", "message_id": message_id }, channel_id ) return None