DGSoft a7ff948e7e Beta Release: Complete Kanban system with auto-save, route persistence, and UI improvements
- Added complete Kanban board functionality with drag-and-drop
- Implemented auto-save for Kanban card editing (no more edit button)
- Added route persistence to remember last visited page on reload
- Improved Kanban UI design with slimmer borders and compact layout
- Added checklist functionality for Kanban cards
- Enhanced file upload and direct messaging features
- Improved authentication and user management
- Added toast notifications system
- Various UI/UX improvements and bug fixes
2025-12-10 23:17:07 +01:00

250 lines
8.4 KiB
Python

from fastapi import APIRouter, Depends, HTTPException, status, BackgroundTasks
from sqlmodel import Session, select
from sqlalchemy.orm import joinedload
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)
.options(joinedload(Message.attachments))
.order_by(Message.created_at.desc())
.offset(offset)
.limit(limit)
)
messages = session.exec(statement).unique().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