2024-01-07 20:18:42 +00:00
|
|
|
import logging
|
|
|
|
|
|
|
|
from django.urls import re_path
|
|
|
|
from django.contrib.auth.decorators import permission_required
|
|
|
|
from rest_framework import routers, viewsets, serializers, status
|
|
|
|
from rest_framework.decorators import api_view, permission_classes
|
|
|
|
from rest_framework.permissions import IsAuthenticated
|
|
|
|
from rest_framework.response import Response
|
|
|
|
from asgiref.sync import async_to_sync
|
|
|
|
from channels.layers import get_channel_layer
|
|
|
|
|
|
|
|
from core.settings import MAIL_DOMAIN
|
|
|
|
from mail.models import Email
|
|
|
|
from mail.protocol import send_smtp, make_reply, collect_references
|
|
|
|
from notify_sessions.models import SystemEvent
|
2023-12-28 20:08:26 +00:00
|
|
|
from tickets.models import IssueThread, Comment, STATE_CHOICES, StateChange
|
2024-01-07 20:18:42 +00:00
|
|
|
|
|
|
|
|
|
|
|
class IssueSerializer(serializers.ModelSerializer):
|
|
|
|
timeline = serializers.SerializerMethodField()
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = IssueThread
|
|
|
|
fields = ('id', 'timeline', 'name', 'state', 'assigned_to', 'last_activity')
|
|
|
|
read_only_fields = ('id', 'timeline', 'last_activity')
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def get_timeline(obj):
|
|
|
|
timeline = []
|
|
|
|
for comment in obj.comments.all():
|
|
|
|
timeline.append({
|
|
|
|
'type': 'comment',
|
|
|
|
'id': comment.id,
|
|
|
|
'timestamp': comment.timestamp,
|
|
|
|
'comment': comment.comment,
|
|
|
|
})
|
|
|
|
for state_change in obj.state_changes.all():
|
|
|
|
timeline.append({
|
|
|
|
'type': 'state',
|
|
|
|
'id': state_change.id,
|
|
|
|
'timestamp': state_change.timestamp,
|
|
|
|
'state': state_change.state,
|
|
|
|
})
|
|
|
|
for email in obj.emails.all():
|
|
|
|
timeline.append({
|
|
|
|
'type': 'mail',
|
|
|
|
'id': email.id,
|
|
|
|
'timestamp': email.timestamp,
|
|
|
|
'sender': email.sender,
|
|
|
|
'recipient': email.recipient,
|
|
|
|
'subject': email.subject,
|
|
|
|
'body': email.body,
|
|
|
|
})
|
|
|
|
return sorted(timeline, key=lambda x: x['timestamp'])
|
|
|
|
|
2023-12-28 20:08:26 +00:00
|
|
|
def update(self, instance, validated_data):
|
|
|
|
if 'state' in validated_data:
|
|
|
|
instance.state = validated_data['state']
|
|
|
|
instance.save()
|
|
|
|
StateChange.objects.create(
|
|
|
|
issue_thread=instance,
|
|
|
|
state=validated_data['state'],
|
|
|
|
)
|
|
|
|
return instance
|
|
|
|
|
2024-01-07 20:18:42 +00:00
|
|
|
|
|
|
|
class IssueViewSet(viewsets.ModelViewSet):
|
|
|
|
serializer_class = IssueSerializer
|
|
|
|
queryset = IssueThread.objects.all()
|
|
|
|
|
|
|
|
|
2023-12-28 20:08:26 +00:00
|
|
|
class CommentSerializer(serializers.ModelSerializer):
|
|
|
|
class Meta:
|
|
|
|
model = Comment
|
|
|
|
fields = ('id', 'comment', 'timestamp', 'issue_thread')
|
|
|
|
|
|
|
|
|
|
|
|
class CommentViewSet(viewsets.ModelViewSet):
|
|
|
|
serializer_class = CommentSerializer
|
|
|
|
queryset = Comment.objects.all()
|
|
|
|
|
|
|
|
|
2024-01-07 20:18:42 +00:00
|
|
|
@api_view(['POST'])
|
|
|
|
@permission_classes([IsAuthenticated])
|
|
|
|
@permission_required('tickets.add_issuethread', raise_exception=True)
|
|
|
|
def reply(request, pk):
|
|
|
|
issue = IssueThread.objects.get(pk=pk)
|
|
|
|
# email = issue.reply(request.data['body']) # TODO evaluate if this is a useful abstraction
|
|
|
|
references = collect_references(issue)
|
|
|
|
most_recent = Email.objects.filter(issue_thread=issue, recipient__endswith='@' + MAIL_DOMAIN).order_by(
|
|
|
|
'-timestamp').first()
|
|
|
|
mail = Email.objects.create(
|
|
|
|
issue_thread=issue,
|
|
|
|
sender=most_recent.recipient,
|
|
|
|
recipient=most_recent.sender,
|
|
|
|
subject=f'Re: {most_recent.subject}',
|
|
|
|
body=request.data['message'],
|
|
|
|
in_reply_to=most_recent.reference,
|
|
|
|
)
|
|
|
|
log = logging.getLogger('mail.log')
|
|
|
|
async_to_sync(send_smtp)(make_reply(mail, references), log)
|
|
|
|
|
|
|
|
return Response({'status': 'ok'}, status=status.HTTP_201_CREATED)
|
|
|
|
|
|
|
|
|
|
|
|
@api_view(['POST'])
|
|
|
|
@permission_classes([IsAuthenticated])
|
|
|
|
@permission_required('tickets.add_issuethread_manual', raise_exception=True)
|
|
|
|
def manual_ticket(request):
|
|
|
|
if 'name' not in request.data:
|
|
|
|
return Response({'status': 'error', 'message': 'missing name'}, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
if 'sender' not in request.data:
|
|
|
|
return Response({'status': 'error', 'message': 'missing sender'}, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
if 'recipient' not in request.data:
|
|
|
|
return Response({'status': 'error', 'message': 'missing recipient'}, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
if 'body' not in request.data:
|
|
|
|
return Response({'status': 'error', 'message': 'missing body'}, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
|
|
|
|
issue = IssueThread.objects.create(
|
|
|
|
name=request.data['name'],
|
|
|
|
manually_created=True,
|
|
|
|
)
|
|
|
|
email = Email.objects.create(
|
|
|
|
issue_thread=issue,
|
|
|
|
sender=request.data['sender'],
|
|
|
|
recipient=request.data['recipient'],
|
|
|
|
subject=request.data['name'],
|
|
|
|
body=request.data['body'],
|
|
|
|
)
|
|
|
|
systemevent = SystemEvent.objects.create(type='email received', reference=email.id)
|
|
|
|
channel_layer = get_channel_layer()
|
|
|
|
async_to_sync(channel_layer.group_send)(
|
|
|
|
'general', {"type": "generic.event", "name": "send_message_to_frontend", "event_id": systemevent.id,
|
|
|
|
"message": "email received"}
|
|
|
|
)
|
|
|
|
|
|
|
|
return Response(IssueSerializer(issue).data, status=status.HTTP_201_CREATED)
|
|
|
|
|
|
|
|
|
2023-12-28 20:08:26 +00:00
|
|
|
class StateSerializer(serializers.Serializer):
|
|
|
|
text = serializers.SerializerMethodField()
|
|
|
|
value = serializers.SerializerMethodField()
|
|
|
|
|
|
|
|
def get_text(self, obj):
|
|
|
|
return obj['text']
|
|
|
|
|
|
|
|
def get_value(self, obj):
|
|
|
|
return obj['value']
|
|
|
|
|
|
|
|
|
|
|
|
@api_view(['GET'])
|
|
|
|
@permission_classes([IsAuthenticated])
|
|
|
|
def get_available_states(request):
|
|
|
|
def get_state_choices():
|
|
|
|
for state in STATE_CHOICES:
|
|
|
|
yield {'value': list(state)[0], 'text': list(state)[1]}
|
|
|
|
return Response(get_state_choices())
|
|
|
|
|
|
|
|
|
2024-01-07 20:18:42 +00:00
|
|
|
router = routers.SimpleRouter()
|
|
|
|
router.register(r'tickets', IssueViewSet, basename='issues')
|
2023-12-28 20:08:26 +00:00
|
|
|
router.register(r'comments', CommentViewSet, basename='comments')
|
2024-01-07 20:18:42 +00:00
|
|
|
|
|
|
|
urlpatterns = ([
|
|
|
|
re_path(r'^tickets/(?P<pk>\d+)/reply/$', reply, name='reply'),
|
|
|
|
re_path(r'^tickets/manual/$', manual_ticket, name='manual_ticket'),
|
2023-12-28 20:08:26 +00:00
|
|
|
re_path(r'^tickets/states/$', get_available_states, name='get_available_states'),
|
2024-01-07 20:18:42 +00:00
|
|
|
] + router.urls)
|