diff --git a/frontend/public/locales/en/common.json b/frontend/public/locales/en/common.json index 795e38948ada4b7c180db25a267d72d19dc00310..ef14baaa3ed4aca5c3f726bc1133a6c4b3b4edb1 100644 --- a/frontend/public/locales/en/common.json +++ b/frontend/public/locales/en/common.json @@ -94,7 +94,8 @@ "save": "Save", "cancel": "Cancel", "backToFrontPage": "Go to front page", - "cancelInvitation": "Cancel" + "cancelInvitation": "Cancel", + "resendInvitation": "Send" }, "registerWizardText": { "registerPage": "Enter the contact information for the guest below. All fields are mandatory.", diff --git a/frontend/public/locales/nb/common.json b/frontend/public/locales/nb/common.json index 2a1a32b4b867ecc9356deac7db1376812f4fed99..6efe20b73ec0688178da2771fa05857e50da8b1f 100644 --- a/frontend/public/locales/nb/common.json +++ b/frontend/public/locales/nb/common.json @@ -94,6 +94,7 @@ "save": "Lagre", "cancel": "Avbryt", "backToFrontPage": "Tilbake til forsiden", + "resendInvitation": "Send", "cancelInvitation": "Kanseller" }, "registerWizardText": { diff --git a/frontend/public/locales/nn/common.json b/frontend/public/locales/nn/common.json index 30dde00a616e4214513de8a3ce1d62e3049e09dd..f7bd06aa336fbf4bcb50ed7918e03e681dad706d 100644 --- a/frontend/public/locales/nn/common.json +++ b/frontend/public/locales/nn/common.json @@ -95,6 +95,7 @@ "save": "Lagre", "cancel": "Avbryt", "backToFrontPage": "Tilbake til forsida", + "resendInvitation": "Send", "cancelInvitation": "Kanseller" }, "registerWizardText": { diff --git a/gregui/api/urls.py b/gregui/api/urls.py index 49a4902f5730cf416cf206b0bfc6bbdaf2a820aa..a3c5e45249170c98958e9c9468426858cd27429c 100644 --- a/gregui/api/urls.py +++ b/gregui/api/urls.py @@ -4,6 +4,8 @@ from rest_framework.routers import DefaultRouter from gregui.api.views.invitation import ( CheckInvitationView, + InvitedGuestView, + ResendInvitationView, InvitationView, InvitedGuestView, ) @@ -22,6 +24,7 @@ urlpatterns += [ path("invited/", InvitedGuestView.as_view(), name="invited-info"), path("invitecheck/", CheckInvitationView.as_view(), name="invite-verify"), path("invite/", InvitationView.as_view(), name="invitation"), + path("resend/<int:person_id>", ResendInvitationView.as_view(), name="invite-resend"), path("person/<int:id>", PersonView.as_view(), name="person-get"), path( "person/search/<searchstring>", PersonSearchView.as_view(), name="person-search" diff --git a/gregui/api/views/invitation.py b/gregui/api/views/invitation.py index 5a293b3f6e5313f5cc81ec8f458586539d575bc1..1c6876d54c8120e8d7fe9fcaf002a8b705c81213 100644 --- a/gregui/api/views/invitation.py +++ b/gregui/api/views/invitation.py @@ -1,3 +1,5 @@ +import datetime +import logging from enum import Enum from typing import Optional @@ -5,20 +7,25 @@ from django.core import exceptions from django.db import transaction from django.http.response import JsonResponse from django.utils import timezone +from django.utils.timezone import now from rest_framework import status from rest_framework.authentication import SessionAuthentication, BasicAuthentication +from rest_framework.generics import CreateAPIView, GenericAPIView +from rest_framework.mixins import UpdateModelMixin from rest_framework.generics import CreateAPIView, GenericAPIView, DestroyAPIView from rest_framework.parsers import JSONParser from rest_framework.permissions import AllowAny from rest_framework.response import Response from rest_framework.views import APIView -from greg.models import Identity, InvitationLink, Person +from greg.models import Identity, InvitationLink, Person, Invitation from greg.permissions import IsSponsor from gregui.api.serializers.guest import GuestRegisterSerializer from gregui.api.serializers.invitation import InviteGuestSerializer from gregui.models import GregUserProfile +logger = logging.getLogger(__name__) + class InvitationView(CreateAPIView, DestroyAPIView): """ @@ -269,9 +276,41 @@ class InvitedGuestView(GenericAPIView): @staticmethod def _get_identity_or_none( - person: Person, identity_type: Identity.IdentityType + person: Person, identity_type: Identity.IdentityType ) -> Optional[str]: try: return person.identities.get(type=identity_type).value except Identity.DoesNotExist: return None + + +class ResendInvitationView(UpdateModelMixin, APIView): + authentication_classes = [BasicAuthentication, SessionAuthentication] + permission_classes = [IsSponsor] + + def patch(self, request, *args, **kwargs) -> Response: + person_id = kwargs["person_id"] + + active_invitation_links = InvitationLink.objects.filter(invitation__role__person__id=person_id, + expire__gte=now()) + + if active_invitation_links.count() == 0: + # No active invitations, it is not expected that this endpoint is called if that is the case + return Response(status=status.HTTP_400_BAD_REQUEST) + + if active_invitation_links.count() > 1: + logger.warning(f"Person with ID {person_id} has multiple active invitation links") + + for link in active_invitation_links: + # TODO Could also update the UUID of the existing invitation link and send it again. Not sure what is best, reusing the existing link-entry in the database or creating a new one as done here + link.expire = now() + link.save() + invitationlink = InvitationLink.objects.create( + invitation=link.invitation, + expire=timezone.now() + datetime.timedelta(days=30), + ) + + # TODO: send email to invited guest + print(invitationlink) + + return Response(status=status.HTTP_200_OK) diff --git a/gregui/api/views/person.py b/gregui/api/views/person.py index e5953a28e325c22f537781295d4d8c813010d4d5..40761b9cb29ba534fc361162e7bc9a7f3389dc6a 100644 --- a/gregui/api/views/person.py +++ b/gregui/api/views/person.py @@ -1,9 +1,10 @@ from django.http.response import JsonResponse +from django.utils.timezone import now from rest_framework.authentication import SessionAuthentication, BasicAuthentication from rest_framework.permissions import IsAuthenticated from rest_framework.views import APIView -from greg.models import Identity, Person +from greg.models import Identity, Person, InvitationLink from greg.permissions import IsSponsor @@ -45,6 +46,8 @@ class PersonView(APIView): } for role in person.roles.all() ], + "has_active_invitations": InvitationLink.objects.filter(invitation__role__person__id=person.id, + expire__gte=now()).count() > 0 } return JsonResponse(response)