from base64 import b64decode from django.urls import re_path from django.contrib.auth.decorators import permission_required from rest_framework import routers, viewsets, 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 inventory.models import Event from mail.models import Email from mail.protocol import send_smtp, make_reply, collect_references from notify_sessions.models import SystemEvent from tickets.models import IssueThread, Comment, STATE_CHOICES, ShippingVoucher, ItemRelation from tickets.serializers import IssueSerializer, CommentSerializer, ShippingVoucherSerializer, SearchResultSerializer from tickets.shared_serializers import RelationSerializer class IssueViewSet(viewsets.ModelViewSet): serializer_class = IssueSerializer queryset = IssueThread.objects.all().prefetch_related('state_changes', 'comments', 'emails', 'emails__attachments', 'assignments', 'assignments__assigned_to__username', 'item_relation_changes', 'shipping_vouchers') class RelationViewSet(viewsets.ModelViewSet): serializer_class = RelationSerializer queryset = ItemRelation.objects.all() class CommentViewSet(viewsets.ModelViewSet): serializer_class = CommentSerializer queryset = Comment.objects.all() class ShippingVoucherViewSet(viewsets.ModelViewSet): serializer_class = ShippingVoucherSerializer queryset = ShippingVoucher.objects.all() @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) first_mail = Email.objects.filter(issue_thread=issue, recipient__endswith='@' + MAIL_DOMAIN).order_by( 'timestamp').first() if not first_mail: return Response({'status': 'error', 'message': 'no previous mail found, reply would not make sense.'}, status=status.HTTP_400_BAD_REQUEST) mail = Email.objects.create( issue_thread=issue, sender=first_mail.recipient, recipient=first_mail.sender, subject=f'Re: {issue.name} [#{issue.short_uuid()}]', body=request.data['message'], in_reply_to=first_mail.reference, ) async_to_sync(send_smtp)(make_reply(mail, references)) 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, event_slug): 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) event = None if event_slug != 'none': try: event = Event.objects.get(slug=event_slug) except: return Response({'status': 'error', 'message': 'invalid event'}, status=status.HTTP_400_BAD_REQUEST) issue = IssueThread.objects.create( name=request.data['name'], event=event, 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) @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()) @api_view(['POST']) @permission_classes([IsAuthenticated]) @permission_required('tickets.add_comment', raise_exception=True) def add_comment(request, pk): issue = IssueThread.objects.get(pk=pk) if 'comment' not in request.data or request.data['comment'] == '': return Response({'status': 'error', 'message': 'missing comment'}, status=status.HTTP_400_BAD_REQUEST) comment = Comment.objects.create( issue_thread=issue, comment=request.data['comment'], ) systemevent = SystemEvent.objects.create(type='comment added', reference=comment.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": "comment added"} ) return Response(CommentSerializer(comment).data, status=status.HTTP_201_CREATED) def filter_issues(issues, query): query_tokens = query.split(' ') for issue in issues: value = 0 for token in query_tokens: if token in issue.description: value += 1 if value > 0: yield {'search_score': value, 'issue': issue} @api_view(['GET']) @permission_classes([]) # @permission_classes([IsAuthenticated]) # @permission_required('view_item', raise_exception=True) def search_issues(request, event_slug, query): try: event = Event.objects.get(slug=event_slug) items = filter_issues(IssueThread.objects.filter(event=event), b64decode(query).decode('utf-8')) return Response(SearchResultSerializer(items, many=True).data) except Event.DoesNotExist: return Response(status=404) router = routers.SimpleRouter() router.register(r'tickets', IssueViewSet, basename='issues') router.register(r'matches', RelationViewSet, basename='matches') router.register(r'shipping_vouchers', ShippingVoucherViewSet, basename='shipping_vouchers') urlpatterns = ([ re_path(r'tickets/states/$', get_available_states, name='get_available_states'), re_path(r'^tickets/(?P\d+)/reply/$', reply, name='reply'), re_path(r'^tickets/(?P\d+)/comment/$', add_comment, name='add_comment'), re_path(r'^(?P[\w-]+)/tickets/manual/$', manual_ticket, name='manual_ticket'), re_path(r'^(?P[\w-]+)/tickets/(?P[-A-Za-z0-9+/]*={0,3})/$', search_issues, name='search_issues'), ] + router.urls)