diff --git a/backend/letters/serializers.py b/backend/letters/serializers.py index 4ed67f7..4cf0afc 100644 --- a/backend/letters/serializers.py +++ b/backend/letters/serializers.py @@ -30,7 +30,7 @@ class LetterSerializer(serializers.ModelSerializer): "updated_at", "images", ] # user to be fetched from request - read_only_fields = ["created_at", "updated_at"] + read_only_fields = ["public_id", "created_at", "updated_at"] def validate(self, data): if (data.get("encrypted_content") or data.get("encrypted_metadata")) and not data.get("encrypted_dek"): diff --git a/backend/letters/tests.py b/backend/letters/tests.py index 94e4863..dc1c015 100644 --- a/backend/letters/tests.py +++ b/backend/letters/tests.py @@ -68,6 +68,34 @@ class LetterAPITest(APITestCase): self.assertEqual(Letter.objects.get().type, "KEPT") self.assertEqual(Letter.objects.get().user, self.user) + def test_update_draft_letter_with_public_id(self): + """Test API can successfully update an existing letter with new values.""" + letter = Letter.objects.create( + user=self.user, + type="KEPT", + status="DRAFT", + public_id="4281edcc-5459-4ff2-bb5e-669fb44e0757", + encrypted_content="enc_xyz==", + encrypted_metadata="enc_meta==", + encrypted_dek="enc_dek==", + ) + payload = { + "public_id": letter.public_id, + "type": "KEPT", + "encrypted_content": "enc_abc==", + "encrypted_metadata": "enc_meta==", + "encrypted_dek": "enc_dek==", + } + response = self.client.put(self.url + letter.public_id + "/", payload) + self.assertEqual(response.status_code, 200) + self.assertEqual(Letter.objects.count(), 1) + self.assertEqual(Letter.objects.get().status, "DRAFT") + self.assertEqual(Letter.objects.get().type, "KEPT") + self.assertEqual(Letter.objects.get().user, self.user) + self.assertEqual(Letter.objects.get().encrypted_content, "enc_abc==") + self.assertEqual(Letter.objects.get().encrypted_metadata, "enc_meta==") + self.assertEqual(Letter.objects.get().encrypted_dek, "enc_dek==") + def test_encrypted_dek_is_required_when_storing_encrypted_content_and_metadata(self): """encrypted_dek is required when encrypted_content and encrypted_metadata are present""" payload = {"type": "KEPT", "encrypted_content": "enc_xyz==", "encrypted_metadata": "enc_meta=="} @@ -88,6 +116,7 @@ class LetterAPITest(APITestCase): image2 = SimpleUploadedFile("enc_img2.bin", b"encrypted_bytes_2", content_type="application/octet-stream") payload = { + "public_id": "4281edcc-5459-4ff2-bb5e-669fb44e0757", "type": "SENT", "status": "SEALED", "encrypted_content": "enc_content==", @@ -96,12 +125,31 @@ class LetterAPITest(APITestCase): "image_files": [image1, image2], } - response = self.client.post(self.url, payload, format="multipart") + response = self.client.put(self.url + payload["public_id"] + "/", payload, format="multipart") self.assertEqual(response.status_code, 201) self.assertEqual(Letter.objects.count(), 1) self.assertEqual(LetterImage.objects.count(), 2) + def test_cleanup_images_when_letter_is_updated(self): + letter = Letter.objects.create(user=self.user, type="KEPT", status="DRAFT") + LetterImage.objects.create(letter=letter, file_name="old1.bin", file=ContentFile(b"data", name="del.bin")) + LetterImage.objects.create(letter=letter, file_name="old2.bin", file=ContentFile(b"data", name="del.bin")) + + response = self.client.put( + f"/api/letters/{letter.public_id}/", + data={ + "encrypted_content": "new_enc==", + "encrypted_metadata": "new_meta==", + "encrypted_dek": "new_dek==", + "image_files": [ContentFile(b"data", name="new.bin")], + }, + format="multipart", + ) + + self.assertEqual(response.status_code, 200) + self.assertEqual(LetterImage.objects.count(), 1) + class LetterImageModelTest(TestCase): def setUp(self): diff --git a/backend/letters/urls.py b/backend/letters/urls.py index aa66f26..4edbc39 100644 --- a/backend/letters/urls.py +++ b/backend/letters/urls.py @@ -4,4 +4,5 @@ from .views import LetterView urlpatterns = [ path("", LetterView.as_view(), name="letter-list-create"), + path("/", LetterView.as_view(), name="letter-create-retrieve-update-delete"), ] diff --git a/backend/letters/views.py b/backend/letters/views.py index 45c20d4..cf6137f 100644 --- a/backend/letters/views.py +++ b/backend/letters/views.py @@ -1,5 +1,6 @@ from rest_framework import generics from rest_framework.permissions import IsAuthenticated +from rest_framework.response import Response from letters.models import Letter, LetterImage from letters.serializers import LetterSerializer @@ -14,8 +15,16 @@ class LetterView(generics.ListCreateAPIView): """return only letters of the authenticated user""" return Letter.objects.filter(user=self.request.user) - def perform_create(self, serializer): - letter = serializer.save(user=self.request.user) - image_files = self.request.FILES.getlist("image_files") - for image_file in image_files: + def put(self, request, public_id): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + + letter, created = Letter.objects.update_or_create( + public_id=public_id, user=request.user, defaults=serializer.validated_data + ) + + LetterImage.objects.filter(letter=letter).delete() + for image_file in request.FILES.getlist("image_files"): LetterImage.objects.create(letter=letter, file=image_file, file_name=image_file.name) + + return Response(serializer.data, status=201 if created else 200)