Coverage for modules\chat\api.py: 36%
58 statements
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-05 09:25 +0800
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-05 09:25 +0800
1from ninja import Router
2from typing import List, Optional
3from uuid import UUID
4from core.auth import JWTAuth
5from core.permissions import require_tenant
6from core.exceptions import NotFoundException
7from .schemas import (
8 ChannelOut, ChannelCreateIn, ChannelMemberOut,
9 MessageOut, MessageSendIn, MessageAttachmentOut, MessageReactionOut
10)
11from .models import Channel, ChannelMember, Message, MessageReaction
12from .services import ChatService
13from modules.authentication.models import User
15router = Router(tags=["Real-Time Chat"])
17@router.get("/channels", auth=JWTAuth(), response={200: List[ChannelOut]})
18def list_channels(request):
19 tenant_id = require_tenant(request)
20 ChatService.get_or_create_general_channel(tenant_id, request.user)
21 channels = Channel.objects.filter(
22 tenant_id=tenant_id,
23 members__user=request.user
24 ).distinct().prefetch_related('messages')
26 result = []
27 for ch in channels:
28 last_msg = ch.messages.order_by('-created_at').select_related('sender').first()
29 last_msg_preview = None
30 if last_msg:
31 last_msg_preview = {
32 'id': last_msg.id,
33 'sender_name': last_msg.sender.full_name,
34 'content': last_msg.content[:50],
35 'created_at': last_msg.created_at,
36 }
38 result.append(ChannelOut(
39 id=ch.id,
40 name=ch.name,
41 topic=ch.topic,
42 type=ch.type,
43 created_by_id=ch.created_by_id,
44 members_count=ch.members.count(),
45 unread_count=0,
46 last_message=last_msg_preview,
47 created_at=ch.created_at,
48 ))
49 return result
51@router.post("/channels", auth=JWTAuth(), response={201: ChannelOut})
52def create_channel(request, payload: ChannelCreateIn):
53 tenant_id = require_tenant(request)
54 ch = ChatService.create_channel(tenant_id, request.user, payload)
55 return 201, ChannelOut(
56 id=ch.id,
57 name=ch.name,
58 topic=ch.topic,
59 type=ch.type,
60 created_by_id=ch.created_by_id,
61 members_count=ch.members.count(),
62 unread_count=0,
63 last_message=None,
64 created_at=ch.created_at,
65 )
67@router.post("/channels/dm", auth=JWTAuth(), response={200: ChannelOut})
68def create_or_get_dm(request, target_user_id: UUID):
69 tenant_id = require_tenant(request)
70 target_user = User.objects.filter(id=target_user_id).first()
71 if not target_user:
72 raise NotFoundException("Target user not found.")
74 ch = ChatService.create_direct_message_channel(tenant_id, request.user, target_user)
75 return ChannelOut(
76 id=ch.id,
77 name=f"Direct: {target_user.full_name}",
78 topic=None,
79 type='DIRECT',
80 created_by_id=ch.created_by_id,
81 members_count=2,
82 unread_count=0,
83 last_message=None,
84 created_at=ch.created_at,
85 )
87@router.get("/channels/{channel_id}/messages", auth=JWTAuth(), response={200: List[MessageOut]})
88def list_messages(request, channel_id: UUID, limit: int = 50):
89 tenant_id = require_tenant(request)
90 channel = Channel.objects.filter(id=channel_id, tenant_id=tenant_id).first()
91 if not channel:
92 raise NotFoundException("Channel not found.")
94 messages = Message.objects.filter(channel=channel).select_related('sender').prefetch_related('attachments', 'reactions')[:limit]
95 result = []
96 for m in messages:
97 reactions_map = {}
98 for r in m.reactions.all():
99 if r.emoji not in reactions_map:
100 reactions_map[r.emoji] = []
101 reactions_map[r.emoji].append({'id': str(r.user_id), 'name': r.user.full_name})
103 result.append(MessageOut(
104 id=m.id,
105 channel_id=m.channel_id,
106 sender_id=m.sender.id,
107 sender_name=m.sender.full_name,
108 sender_avatar=m.sender.avatar_url,
109 content=m.content,
110 parent_id=m.parent_id,
111 attachments=[
112 MessageAttachmentOut(
113 id=a.id,
114 file_name=a.file_name,
115 file_size=a.file_size,
116 file_type=a.file_type,
117 file_url=a.file_url,
118 ) for a in m.attachments.all()
119 ],
120 reactions=[
121 MessageReactionOut(
122 emoji=emoji,
123 count=len(users),
124 users=users
125 ) for emoji, users in reactions_map.items()
126 ],
127 is_pinned=m.is_pinned,
128 is_edited=m.is_edited,
129 created_at=m.created_at,
130 updated_at=m.created_at,
131 ))
132 return result
134@router.post("/channels/{channel_id}/messages", auth=JWTAuth(), response={201: MessageOut})
135def send_message(request, channel_id: UUID, payload: MessageSendIn):
136 tenant_id = require_tenant(request)
137 msg = ChatService.send_message(tenant_id, request.user, channel_id, payload)
138 return 201, MessageOut(
139 id=msg.id,
140 channel_id=msg.channel_id,
141 sender_id=msg.sender.id,
142 sender_name=msg.sender.full_name,
143 sender_avatar=msg.sender.avatar_url,
144 content=msg.content,
145 parent_id=msg.parent_id,
146 attachments=[],
147 reactions=[],
148 is_pinned=msg.is_pinned,
149 is_edited=msg.is_edited,
150 created_at=msg.created_at,
151 updated_at=msg.created_at,
152 )