【悟凡】真正意义上的净土重生:只保留核心逻辑
This commit is contained in:
0
backend/meetings/__init__.py
Normal file
0
backend/meetings/__init__.py
Normal file
3
backend/meetings/admin.py
Normal file
3
backend/meetings/admin.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
6
backend/meetings/apps.py
Normal file
6
backend/meetings/apps.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class MeetingsConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "meetings"
|
||||
248
backend/meetings/migrations/0001_initial.py
Normal file
248
backend/meetings/migrations/0001_initial.py
Normal file
@@ -0,0 +1,248 @@
|
||||
# Generated by Django 4.2 on 2026-04-04 01:23
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="Meeting",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.UUIDField(
|
||||
default=uuid.uuid4,
|
||||
editable=False,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
),
|
||||
),
|
||||
("topic", models.CharField(max_length=200, verbose_name="会议主题")),
|
||||
(
|
||||
"status",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("pending", "待开始"),
|
||||
("active", "进行中"),
|
||||
("ended", "已结束"),
|
||||
],
|
||||
default="pending",
|
||||
max_length=20,
|
||||
),
|
||||
),
|
||||
(
|
||||
"invite_code",
|
||||
models.CharField(max_length=20, unique=True, verbose_name="邀请码"),
|
||||
),
|
||||
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||
("started_at", models.DateTimeField(blank=True, null=True)),
|
||||
("ended_at", models.DateTimeField(blank=True, null=True)),
|
||||
(
|
||||
"host",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="hosted_meetings",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "会议室",
|
||||
"verbose_name_plural": "会议室",
|
||||
"db_table": "meetings",
|
||||
"ordering": ["-created_at"],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Participant",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"agent_type",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("human", "人类"),
|
||||
("openclaw", "OpenClaw Agent"),
|
||||
("other", "其他 AI"),
|
||||
],
|
||||
max_length=20,
|
||||
),
|
||||
),
|
||||
("agent_id", models.CharField(blank=True, max_length=100, null=True)),
|
||||
(
|
||||
"agent_name",
|
||||
models.CharField(max_length=100, verbose_name="Agent 名称"),
|
||||
),
|
||||
(
|
||||
"agent_emoji",
|
||||
models.CharField(
|
||||
default="🤖", max_length=10, verbose_name="Agent 表情"
|
||||
),
|
||||
),
|
||||
("nickname", models.CharField(max_length=100, verbose_name="昵称")),
|
||||
("is_host", models.BooleanField(default=False)),
|
||||
("joined_at", models.DateTimeField(auto_now_add=True)),
|
||||
("left_at", models.DateTimeField(blank=True, null=True)),
|
||||
("api_key", models.CharField(blank=True, max_length=255, null=True)),
|
||||
(
|
||||
"meeting",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="participants",
|
||||
to="meetings.meeting",
|
||||
),
|
||||
),
|
||||
(
|
||||
"user",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "参会者",
|
||||
"verbose_name_plural": "参会者",
|
||||
"db_table": "participants",
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Message",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("content", models.TextField()),
|
||||
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||
(
|
||||
"is_broadcast",
|
||||
models.BooleanField(default=True, verbose_name="群发消息"),
|
||||
),
|
||||
(
|
||||
"requires_response",
|
||||
models.BooleanField(default=False, verbose_name="需要回复"),
|
||||
),
|
||||
(
|
||||
"in_reply_to",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="replies",
|
||||
to="meetings.message",
|
||||
),
|
||||
),
|
||||
(
|
||||
"meeting",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="messages",
|
||||
to="meetings.meeting",
|
||||
),
|
||||
),
|
||||
(
|
||||
"read_by",
|
||||
models.ManyToManyField(
|
||||
blank=True,
|
||||
related_name="read_messages",
|
||||
to="meetings.participant",
|
||||
),
|
||||
),
|
||||
(
|
||||
"sender",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="sent_messages",
|
||||
to="meetings.participant",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "消息",
|
||||
"verbose_name_plural": "消息",
|
||||
"db_table": "messages",
|
||||
"ordering": ["created_at"],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="MeetingMinutes",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("content", models.TextField()),
|
||||
("generated_at", models.DateTimeField(auto_now_add=True)),
|
||||
("exported_at", models.DateTimeField(blank=True, null=True)),
|
||||
(
|
||||
"meeting",
|
||||
models.OneToOneField(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="minutes",
|
||||
to="meetings.meeting",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "会议纪要",
|
||||
"verbose_name_plural": "会议纪要",
|
||||
"db_table": "meeting_minutes",
|
||||
},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="participant",
|
||||
index=models.Index(
|
||||
fields=["meeting", "agent_id"], name="participant_meeting_74488d_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="participant",
|
||||
index=models.Index(
|
||||
fields=["agent_type", "meeting"], name="participant_agent_t_c4a9dc_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="message",
|
||||
index=models.Index(
|
||||
fields=["meeting", "created_at"], name="messages_meeting_b69008_idx"
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="message",
|
||||
index=models.Index(
|
||||
fields=["is_broadcast", "created_at"],
|
||||
name="messages_is_broa_fae706_idx",
|
||||
),
|
||||
),
|
||||
]
|
||||
0
backend/meetings/migrations/__init__.py
Normal file
0
backend/meetings/migrations/__init__.py
Normal file
126
backend/meetings/models.py
Normal file
126
backend/meetings/models.py
Normal file
@@ -0,0 +1,126 @@
|
||||
from django.db import models
|
||||
from django.contrib.auth import get_user_model
|
||||
import uuid
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class Meeting(models.Model):
|
||||
"""会议室模型"""
|
||||
STATUS_CHOICES = [
|
||||
('pending', '待开始'),
|
||||
('active', '进行中'),
|
||||
('ended', '已结束'),
|
||||
]
|
||||
|
||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||
topic = models.CharField(max_length=200, verbose_name='会议主题')
|
||||
host = models.ForeignKey(User, on_delete=models.CASCADE, related_name='hosted_meetings')
|
||||
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending')
|
||||
invite_code = models.CharField(max_length=20, unique=True, verbose_name='邀请码')
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
started_at = models.DateTimeField(null=True, blank=True)
|
||||
ended_at = models.DateTimeField(null=True, blank=True)
|
||||
|
||||
class Meta:
|
||||
db_table = 'meetings'
|
||||
verbose_name = '会议室'
|
||||
verbose_name_plural = '会议室'
|
||||
ordering = ['-created_at']
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.topic} ({self.host.username})"
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.invite_code:
|
||||
self.invite_code = uuid.uuid4().hex[:8].upper()
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
|
||||
class Participant(models.Model):
|
||||
"""参会者模型"""
|
||||
AGENT_TYPE_CHOICES = [
|
||||
('human', '人类'),
|
||||
('openclaw', 'OpenClaw Agent'),
|
||||
('other', '其他 AI'),
|
||||
]
|
||||
|
||||
meeting = models.ForeignKey(Meeting, on_delete=models.CASCADE, related_name='participants')
|
||||
user = models.ForeignKey(User, null=True, blank=True, on_delete=models.CASCADE)
|
||||
|
||||
# Agent 信息
|
||||
agent_type = models.CharField(max_length=20, choices=AGENT_TYPE_CHOICES)
|
||||
agent_id = models.CharField(max_length=100, null=True, blank=True)
|
||||
agent_name = models.CharField(max_length=100, verbose_name='Agent 名称')
|
||||
agent_emoji = models.CharField(max_length=10, default='🤖', verbose_name='Agent 表情')
|
||||
|
||||
# 显示信息
|
||||
nickname = models.CharField(max_length=100, verbose_name='昵称')
|
||||
is_host = models.BooleanField(default=False)
|
||||
joined_at = models.DateTimeField(auto_now_add=True)
|
||||
left_at = models.DateTimeField(null=True, blank=True)
|
||||
|
||||
# API 认证(Agent 用)
|
||||
api_key = models.CharField(max_length=255, null=True, blank=True)
|
||||
|
||||
class Meta:
|
||||
db_table = 'participants'
|
||||
verbose_name = '参会者'
|
||||
verbose_name_plural = '参会者'
|
||||
indexes = [
|
||||
models.Index(fields=['meeting', 'agent_id']),
|
||||
models.Index(fields=['agent_type', 'meeting']),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.agent_emoji} {self.nickname}"
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.api_key and self.agent_type != 'human':
|
||||
self.api_key = uuid.uuid4().hex
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
|
||||
class Message(models.Model):
|
||||
"""消息模型"""
|
||||
meeting = models.ForeignKey(Meeting, on_delete=models.CASCADE, related_name='messages')
|
||||
sender = models.ForeignKey(Participant, on_delete=models.CASCADE, related_name='sent_messages')
|
||||
content = models.TextField()
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
# 信箱机制
|
||||
is_broadcast = models.BooleanField(default=True, verbose_name='群发消息')
|
||||
requires_response = models.BooleanField(default=False, verbose_name='需要回复')
|
||||
in_reply_to = models.ForeignKey('self', null=True, blank=True, on_delete=models.SET_NULL, related_name='replies')
|
||||
|
||||
# 读取状态
|
||||
read_by = models.ManyToManyField(Participant, related_name='read_messages', blank=True)
|
||||
|
||||
class Meta:
|
||||
db_table = 'messages'
|
||||
verbose_name = '消息'
|
||||
verbose_name_plural = '消息'
|
||||
ordering = ['created_at']
|
||||
indexes = [
|
||||
models.Index(fields=['meeting', 'created_at']),
|
||||
models.Index(fields=['is_broadcast', 'created_at']),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.sender.nickname}: {self.content[:50]}"
|
||||
|
||||
|
||||
class MeetingMinutes(models.Model):
|
||||
"""会议纪要模型"""
|
||||
meeting = models.OneToOneField(Meeting, on_delete=models.CASCADE, related_name='minutes')
|
||||
content = models.TextField()
|
||||
generated_at = models.DateTimeField(auto_now_add=True)
|
||||
exported_at = models.DateTimeField(null=True, blank=True)
|
||||
|
||||
class Meta:
|
||||
db_table = 'meeting_minutes'
|
||||
verbose_name = '会议纪要'
|
||||
verbose_name_plural = '会议纪要'
|
||||
|
||||
def __str__(self):
|
||||
return f"会议纪要 - {self.meeting.topic}"
|
||||
76
backend/meetings/serializers.py
Normal file
76
backend/meetings/serializers.py
Normal file
@@ -0,0 +1,76 @@
|
||||
from rest_framework import serializers
|
||||
from .models import Meeting, Participant, Message, MeetingMinutes
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class UserSerializer(serializers.ModelSerializer):
|
||||
"""用户序列化器"""
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ['id', 'username', 'email', 'created_at']
|
||||
|
||||
|
||||
class MeetingSerializer(serializers.ModelSerializer):
|
||||
"""会议室序列化器"""
|
||||
host_name = serializers.CharField(source='host.username', read_only=True)
|
||||
participant_count = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = Meeting
|
||||
fields = [
|
||||
'id', 'topic', 'host', 'host_name', 'status', 'invite_code',
|
||||
'created_at', 'started_at', 'ended_at', 'participant_count'
|
||||
]
|
||||
read_only_fields = ['host', 'invite_code', 'status']
|
||||
|
||||
def get_participant_count(self, obj):
|
||||
return obj.participants.filter(left_at__isnull=True).count()
|
||||
|
||||
def create(self, validated_data):
|
||||
# 使用第一个用户作为 host(临时方案)
|
||||
host = User.objects.first()
|
||||
validated_data['host'] = host
|
||||
return super().create(validated_data)
|
||||
|
||||
|
||||
class ParticipantSerializer(serializers.ModelSerializer):
|
||||
"""参会者序列化器"""
|
||||
class Meta:
|
||||
model = Participant
|
||||
fields = [
|
||||
'id', 'meeting', 'user', 'agent_type', 'agent_id',
|
||||
'agent_name', 'agent_emoji', 'nickname', 'is_host',
|
||||
'joined_at', 'api_key'
|
||||
]
|
||||
read_only_fields = ['api_key', 'joined_at']
|
||||
|
||||
|
||||
class MessageSerializer(serializers.ModelSerializer):
|
||||
"""消息序列化器"""
|
||||
sender_name = serializers.CharField(source='sender.nickname', read_only=True)
|
||||
sender_emoji = serializers.CharField(source='sender.agent_emoji', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Message
|
||||
fields = [
|
||||
'id', 'meeting', 'sender', 'sender_name', 'sender_emoji',
|
||||
'content', 'created_at', 'is_broadcast', 'requires_response',
|
||||
'in_reply_to', 'read_by'
|
||||
]
|
||||
read_only_fields = ['sender', 'created_at']
|
||||
|
||||
|
||||
class InboxSerializer(serializers.Serializer):
|
||||
"""信箱序列化器"""
|
||||
unread_count = serializers.IntegerField()
|
||||
messages = MessageSerializer(many=True)
|
||||
|
||||
|
||||
class MeetingMinutesSerializer(serializers.ModelSerializer):
|
||||
"""会议纪要序列化器"""
|
||||
class Meta:
|
||||
model = MeetingMinutes
|
||||
fields = ['meeting', 'content', 'generated_at', 'exported_at']
|
||||
read_only_fields = ['meeting', 'generated_at']
|
||||
3
backend/meetings/tests.py
Normal file
3
backend/meetings/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
236
backend/meetings/views.py
Normal file
236
backend/meetings/views.py
Normal file
@@ -0,0 +1,236 @@
|
||||
from rest_framework import viewsets, status, permissions
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
from django.utils import timezone
|
||||
from .models import Meeting, Participant, Message, MeetingMinutes
|
||||
from .serializers import (
|
||||
MeetingSerializer, ParticipantSerializer,
|
||||
MessageSerializer, InboxSerializer
|
||||
)
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.shortcuts import get_object_or_404
|
||||
import uuid
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class MeetingViewSet(viewsets.ModelViewSet):
|
||||
"""会议室视图集"""
|
||||
queryset = Meeting.objects.all()
|
||||
serializer_class = MeetingSerializer
|
||||
permission_classes = [] # 临时开放所有权限
|
||||
|
||||
def get_queryset(self):
|
||||
# 简单返回所有会议
|
||||
return Meeting.objects.all().order_by('-created_at')
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
"""创建会议"""
|
||||
serializer = self.get_serializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
# 临时:使用第一个用户作为 host
|
||||
from django.contrib.auth import get_user_model
|
||||
User = get_user_model()
|
||||
host = User.objects.first()
|
||||
|
||||
meeting = serializer.save(host=host)
|
||||
|
||||
# 创建主持人参会记录
|
||||
Participant.objects.create(
|
||||
meeting=meeting,
|
||||
user=host,
|
||||
agent_type='human',
|
||||
nickname=host.username,
|
||||
is_host=True
|
||||
)
|
||||
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
|
||||
@action(detail=True, methods=['post'])
|
||||
def start(self, request, pk=None):
|
||||
"""开始会议"""
|
||||
meeting = self.get_object()
|
||||
if meeting.host != request.user:
|
||||
return Response(
|
||||
{'error': '只有主持人可以开始会议'},
|
||||
status=status.HTTP_403_FORBIDDEN
|
||||
)
|
||||
|
||||
meeting.status = 'active'
|
||||
meeting.started_at = timezone.now()
|
||||
meeting.save()
|
||||
|
||||
return Response({'status': '会议已开始'})
|
||||
|
||||
@action(detail=True, methods=['post'])
|
||||
def end(self, request, pk=None):
|
||||
"""结束会议"""
|
||||
meeting = self.get_object()
|
||||
if meeting.host != request.user:
|
||||
return Response(
|
||||
{'error': '只有主持人可以结束会议'},
|
||||
status=status.HTTP_403_FORBIDDEN
|
||||
)
|
||||
|
||||
meeting.status = 'ended'
|
||||
meeting.ended_at = timezone.now()
|
||||
meeting.save()
|
||||
|
||||
return Response({'status': '会议已结束'})
|
||||
|
||||
@action(detail=True, methods=['post'])
|
||||
def join(self, request, pk=None):
|
||||
"""加入会议"""
|
||||
meeting = self.get_object()
|
||||
|
||||
if meeting.status == 'ended':
|
||||
return Response(
|
||||
{'error': '会议已结束'},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
invite_code = request.data.get('invite_code')
|
||||
if invite_code != meeting.invite_code:
|
||||
return Response(
|
||||
{'error': '邀请码错误'},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
# 检查是否已加入
|
||||
existing = Participant.objects.filter(
|
||||
meeting=meeting,
|
||||
user=request.user,
|
||||
left_at__isnull=True
|
||||
).first()
|
||||
|
||||
if existing:
|
||||
return Response(ParticipantSerializer(existing).data)
|
||||
|
||||
# 创建参会记录
|
||||
participant = Participant.objects.create(
|
||||
meeting=meeting,
|
||||
user=request.user,
|
||||
agent_type='human',
|
||||
nickname=request.user.username
|
||||
)
|
||||
|
||||
return Response(ParticipantSerializer(participant).data, status=status.HTTP_201_CREATED)
|
||||
|
||||
@action(detail=True, methods=['get'])
|
||||
def participants(self, request, pk=None):
|
||||
"""获取参会者列表"""
|
||||
meeting = self.get_object()
|
||||
participants = meeting.participants.filter(left_at__isnull=True)
|
||||
serializer = ParticipantSerializer(participants, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
@action(detail=True, methods=['get'])
|
||||
def messages(self, request, pk=None):
|
||||
"""获取消息(人类轮询)"""
|
||||
meeting = self.get_object()
|
||||
last_id = request.query_params.get('last_id', 0)
|
||||
|
||||
messages = meeting.messages.filter(id__gt=last_id).select_related('sender')
|
||||
serializer = MessageSerializer(messages, many=True)
|
||||
|
||||
return Response({'messages': serializer.data})
|
||||
|
||||
@action(detail=True, methods=['post'])
|
||||
def send_message(self, request, pk=None):
|
||||
"""发送消息"""
|
||||
meeting = self.get_object()
|
||||
|
||||
# 获取或创建参会者(临时:使用第一个参会者或创建)
|
||||
participant = Participant.objects.filter(
|
||||
meeting=meeting,
|
||||
left_at__isnull=True
|
||||
).first()
|
||||
|
||||
if not participant:
|
||||
# 创建默认参会者
|
||||
host = meeting.host
|
||||
participant = Participant.objects.create(
|
||||
meeting=meeting,
|
||||
user=host,
|
||||
agent_type='human',
|
||||
nickname=host.username
|
||||
)
|
||||
|
||||
content = request.data.get('content')
|
||||
if not content:
|
||||
return Response(
|
||||
{'error': '消息内容不能为空'},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
message = Message.objects.create(
|
||||
meeting=meeting,
|
||||
sender=participant,
|
||||
content=content,
|
||||
is_broadcast=request.data.get('is_broadcast', True),
|
||||
requires_response=request.data.get('requires_response', False)
|
||||
)
|
||||
|
||||
return Response(MessageSerializer(message).data, status=status.HTTP_201_CREATED)
|
||||
|
||||
@action(detail=True, methods=['get'])
|
||||
def inbox(self, request, pk=None):
|
||||
"""Agent 查阅信箱"""
|
||||
meeting = self.get_object()
|
||||
agent_id = request.query_params.get('agent_id')
|
||||
|
||||
if not agent_id:
|
||||
return Response(
|
||||
{'error': '缺少 agent_id 参数'},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
# 找到这个 Agent 的参会记录
|
||||
participant = get_object_or_404(
|
||||
Participant,
|
||||
meeting=meeting,
|
||||
agent_id=agent_id,
|
||||
left_at__isnull=True
|
||||
)
|
||||
|
||||
# 获取发给这个 Agent 的消息(未读)
|
||||
messages = Message.objects.filter(
|
||||
meeting=meeting
|
||||
).exclude(
|
||||
read_by=participant
|
||||
)
|
||||
|
||||
# 如果是群发消息,所有人都能看到
|
||||
# 如果是指定消息,需要检查 recipients
|
||||
# 简化版:所有未读消息都返回
|
||||
|
||||
serializer = MessageSerializer(messages, many=True)
|
||||
|
||||
return Response({
|
||||
'unread_count': messages.count(),
|
||||
'messages': serializer.data
|
||||
})
|
||||
|
||||
|
||||
class ParticipantViewSet(viewsets.ModelViewSet):
|
||||
"""参会者视图集"""
|
||||
queryset = Participant.objects.all()
|
||||
serializer_class = ParticipantSerializer
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
|
||||
@action(detail=True, methods=['post'])
|
||||
def leave(self, request, pk=None):
|
||||
"""离开会议"""
|
||||
participant = self.get_object()
|
||||
|
||||
if participant.user != request.user:
|
||||
return Response(
|
||||
{'error': '无权操作'},
|
||||
status=status.HTTP_403_FORBIDDEN
|
||||
)
|
||||
|
||||
participant.left_at = timezone.now()
|
||||
participant.save()
|
||||
|
||||
return Response({'status': '已离开会议'})
|
||||
Reference in New Issue
Block a user