DGSoft 93b98cfb5c Initial commit: Team Chat System with Code Snippet Library
- Complete chat application similar to Microsoft Teams
- Code snippet library with syntax highlighting
- Real-time messaging with WebSockets
- File upload with Office integration
- Department-based permissions
- Dark/Light theme support
- Production deployment with SSL/Reverse Proxy
- Docker containerization
- PostgreSQL database with SQLModel ORM
2025-12-09 22:25:03 +01:00

248 lines
8.3 KiB
Python

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