mirror of
https://github.com/OHV-IT/collabrix.git
synced 2025-12-16 00:58:37 +01:00
- Implement unread message indicators with Material-UI icons - Add BlinkingEnvelope component with theme-compatible colors - Create UnreadMessagesContext for managing unread states - Integrate WebSocket message handling for real-time notifications - Icons only appear for inactive channels/DMs, disappear when opened - Add test functionality (double-click to mark as unread) - Fix WebSocket URL handling for production deployment - Unify WebSocket architecture using presence connection for all messages
105 lines
3.9 KiB
Python
105 lines
3.9 KiB
Python
from fastapi import APIRouter, WebSocket, WebSocketDisconnect, Depends, Query
|
|
from sqlmodel import Session
|
|
from app.database import get_session
|
|
from app.websocket import manager
|
|
from app.auth import decode_access_token, get_current_user
|
|
from app.models import User, Channel, UserRole
|
|
from sqlmodel import select
|
|
import json
|
|
|
|
router = APIRouter(tags=["WebSocket"])
|
|
|
|
|
|
@router.websocket("/ws/{channel_id}")
|
|
async def websocket_endpoint(
|
|
websocket: WebSocket,
|
|
channel_id: int,
|
|
token: str = Query(...),
|
|
):
|
|
"""WebSocket endpoint for real-time channel messages and direct messages"""
|
|
# Authenticate user via token
|
|
username = decode_access_token(token)
|
|
if not username:
|
|
await websocket.close(code=1008, reason="Invalid authentication")
|
|
return
|
|
|
|
# Create a session for database operations
|
|
from app.database import engine
|
|
with Session(engine) as session:
|
|
# Verify user exists
|
|
statement = select(User).where(User.username == username)
|
|
user = session.exec(statement).first()
|
|
if not user:
|
|
await websocket.close(code=1008, reason="User not found")
|
|
return
|
|
|
|
# Negative channel_id means direct messages (user_id)
|
|
# channel_id 0 means presence-only connection
|
|
if channel_id < 0:
|
|
# Direct message connection - verify it's the user's own connection
|
|
if -channel_id != user.id:
|
|
await websocket.close(code=1008, reason="Access denied")
|
|
return
|
|
elif channel_id > 0:
|
|
# Regular channel - verify channel exists and user has access
|
|
channel = session.get(Channel, channel_id)
|
|
if not channel:
|
|
await websocket.close(code=1008, reason="Channel not found")
|
|
return
|
|
|
|
user_dept_ids = [dept.id for dept in user.departments]
|
|
if channel.department_id not in user_dept_ids and user.role != UserRole.SUPERADMIN:
|
|
await websocket.close(code=1008, reason="Access denied")
|
|
return
|
|
# channel_id 0 is allowed for presence-only connections
|
|
|
|
# Connect to channel
|
|
await manager.connect(websocket, channel_id, user.id)
|
|
|
|
# Broadcast user status update (online)
|
|
await manager.broadcast_user_status_update(user.id, "online")
|
|
|
|
try:
|
|
# Send welcome message
|
|
await manager.send_personal_message(
|
|
json.dumps({
|
|
"type": "system",
|
|
"message": f"Connected to channel {channel_id}"
|
|
}),
|
|
websocket
|
|
)
|
|
|
|
# Listen for messages
|
|
while True:
|
|
data = await websocket.receive_text()
|
|
|
|
# Echo back or process the message
|
|
# In production, you'd save to DB and broadcast
|
|
try:
|
|
message_data = json.loads(data)
|
|
# Broadcast to all clients in the channel
|
|
await manager.broadcast_to_channel(
|
|
{
|
|
"type": "message",
|
|
"content": message_data.get("content", ""),
|
|
"sender": username,
|
|
"channel_id": channel_id
|
|
},
|
|
channel_id
|
|
)
|
|
except json.JSONDecodeError:
|
|
await manager.send_personal_message(
|
|
json.dumps({"type": "error", "message": "Invalid JSON"}),
|
|
websocket
|
|
)
|
|
|
|
except WebSocketDisconnect:
|
|
manager.disconnect(websocket, channel_id, user.id)
|
|
# Broadcast user status update (offline)
|
|
await manager.broadcast_user_status_update(user.id, "offline")
|
|
except Exception as e:
|
|
manager.disconnect(websocket, channel_id, user.id)
|
|
# Broadcast user status update (offline)
|
|
await manager.broadcast_user_status_update(user.id, "offline")
|
|
print(f"WebSocket error: {e}")
|