2024-11-20 23:58:14 +00:00
|
|
|
from base64 import b64decode
|
2024-01-07 20:18:42 +00:00
|
|
|
|
|
|
|
from django.urls import re_path
|
|
|
|
from django.contrib.auth.decorators import permission_required
|
2024-01-22 16:21:22 +00:00
|
|
|
from rest_framework import routers, viewsets, status
|
2024-01-07 20:18:42 +00:00
|
|
|
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
|
2024-11-05 23:03:35 +00:00
|
|
|
from inventory.models import Event
|
2024-01-07 20:18:42 +00:00
|
|
|
from mail.models import Email
|
|
|
|
from mail.protocol import send_smtp, make_reply, collect_references
|
|
|
|
from notify_sessions.models import SystemEvent
|
2024-11-20 23:58:14 +00:00
|
|
|
from tickets.models import IssueThread, Comment, STATE_CHOICES, ShippingVoucher, ItemRelation
|
2024-11-09 04:05:05 +00:00
|
|
|
from tickets.serializers import IssueSerializer, CommentSerializer, ShippingVoucherSerializer, SearchResultSerializer
|
2024-11-20 23:58:14 +00:00
|
|
|
from tickets.shared_serializers import RelationSerializer
|
2024-01-10 19:03:25 +00:00
|
|
|
|
2024-01-07 20:18:42 +00:00
|
|
|
|
|
|
|
class IssueViewSet(viewsets.ModelViewSet):
|
|
|
|
serializer_class = IssueSerializer
|
2024-11-23 00:35:05 +00:00
|
|
|
queryset = IssueThread.objects.all().prefetch_related('state_changes', 'comments', 'emails', 'emails__attachments',
|
|
|
|
'assignments', 'item_relation_changes', 'shipping_vouchers')
|
2024-01-07 20:18:42 +00:00
|
|
|
|
|
|
|
|
2024-11-20 23:58:14 +00:00
|
|
|
class RelationViewSet(viewsets.ModelViewSet):
|
|
|
|
serializer_class = RelationSerializer
|
|
|
|
queryset = ItemRelation.objects.all()
|
|
|
|
|
|
|
|
|
|
|
|
class CommentViewSet(viewsets.ModelViewSet):
|
|
|
|
serializer_class = CommentSerializer
|
|
|
|
queryset = Comment.objects.all()
|
|
|
|
|
|
|
|
|
2024-06-23 00:50:44 +00:00
|
|
|
class ShippingVoucherViewSet(viewsets.ModelViewSet):
|
|
|
|
serializer_class = ShippingVoucherSerializer
|
|
|
|
queryset = ShippingVoucher.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)
|
2024-01-15 21:00:03 +00:00
|
|
|
first_mail = Email.objects.filter(issue_thread=issue, recipient__endswith='@' + MAIL_DOMAIN).order_by(
|
|
|
|
'timestamp').first()
|
|
|
|
if not first_mail:
|
2024-01-22 16:21:22 +00:00
|
|
|
return Response({'status': 'error', 'message': 'no previous mail found, reply would not make sense.'},
|
|
|
|
status=status.HTTP_400_BAD_REQUEST)
|
2024-01-07 20:18:42 +00:00
|
|
|
mail = Email.objects.create(
|
|
|
|
issue_thread=issue,
|
2024-01-15 21:00:03 +00:00
|
|
|
sender=first_mail.recipient,
|
|
|
|
recipient=first_mail.sender,
|
|
|
|
subject=f'Re: {issue.name} [#{issue.short_uuid()}]',
|
2024-01-07 20:18:42 +00:00
|
|
|
body=request.data['message'],
|
2024-01-15 21:00:03 +00:00
|
|
|
in_reply_to=first_mail.reference,
|
2024-01-07 20:18:42 +00:00
|
|
|
)
|
2024-06-22 23:20:13 +00:00
|
|
|
async_to_sync(send_smtp)(make_reply(mail, references))
|
2024-01-07 20:18:42 +00:00
|
|
|
|
|
|
|
return Response({'status': 'ok'}, status=status.HTTP_201_CREATED)
|
|
|
|
|
|
|
|
|
|
|
|
@api_view(['POST'])
|
|
|
|
@permission_classes([IsAuthenticated])
|
|
|
|
@permission_required('tickets.add_issuethread_manual', raise_exception=True)
|
2024-11-13 22:51:54 +00:00
|
|
|
def manual_ticket(request, event_slug):
|
2024-01-07 20:18:42 +00:00
|
|
|
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)
|
|
|
|
|
2024-11-13 22:51:54 +00:00
|
|
|
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)
|
|
|
|
|
2024-01-07 20:18:42 +00:00
|
|
|
issue = IssueThread.objects.create(
|
|
|
|
name=request.data['name'],
|
2024-11-13 22:51:54 +00:00
|
|
|
event=event,
|
2024-01-07 20:18:42 +00:00
|
|
|
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
|
|
|
@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]}
|
2023-12-30 17:34:35 +00:00
|
|
|
|
2023-12-28 20:08:26 +00:00
|
|
|
return Response(get_state_choices())
|
|
|
|
|
|
|
|
|
2024-01-13 00:40:37 +00:00
|
|
|
@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)
|
|
|
|
|
|
|
|
|
2024-11-09 04:05:05 +00:00
|
|
|
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'])
|
2024-11-22 23:59:45 +00:00
|
|
|
@permission_classes([IsAuthenticated])
|
2024-11-09 04:05:05 +00:00
|
|
|
def search_issues(request, event_slug, query):
|
|
|
|
try:
|
|
|
|
event = Event.objects.get(slug=event_slug)
|
2024-11-22 23:59:45 +00:00
|
|
|
if not request.user.has_event_perm(event, 'view_issuethread'):
|
|
|
|
return Response(status=403)
|
2024-11-09 04:05:05 +00:00
|
|
|
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)
|
|
|
|
|
|
|
|
|
2024-01-07 20:18:42 +00:00
|
|
|
router = routers.SimpleRouter()
|
|
|
|
router.register(r'tickets', IssueViewSet, basename='issues')
|
2024-11-20 23:58:14 +00:00
|
|
|
router.register(r'matches', RelationViewSet, basename='matches')
|
2024-06-23 00:50:44 +00:00
|
|
|
router.register(r'shipping_vouchers', ShippingVoucherViewSet, basename='shipping_vouchers')
|
2024-01-07 20:18:42 +00:00
|
|
|
|
2024-07-18 19:11:53 +00:00
|
|
|
# [-A-Za-z0-9+/]*={0,3}
|
2024-01-07 20:18:42 +00:00
|
|
|
urlpatterns = ([
|
2024-11-13 22:51:54 +00:00
|
|
|
re_path(r'tickets/states/$', get_available_states, name='get_available_states'),
|
2024-01-07 20:18:42 +00:00
|
|
|
re_path(r'^tickets/(?P<pk>\d+)/reply/$', reply, name='reply'),
|
2024-01-13 00:40:37 +00:00
|
|
|
re_path(r'^tickets/(?P<pk>\d+)/comment/$', add_comment, name='add_comment'),
|
2024-11-13 22:51:54 +00:00
|
|
|
re_path(r'^(?P<event_slug>[\w-]+)/tickets/manual/$', manual_ticket, name='manual_ticket'),
|
2024-11-09 04:05:05 +00:00
|
|
|
re_path(r'^(?P<event_slug>[\w-]+)/tickets/(?P<query>[-A-Za-z0-9+/]*={0,3})/$', search_issues,
|
2024-11-20 23:34:00 +00:00
|
|
|
name='search_issues'),
|
2024-01-07 20:18:42 +00:00
|
|
|
] + router.urls)
|