From e2b1ce55a469f0331c97cc9e023194cf9de48714 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 12 Apr 2026 01:51:32 +0530 Subject: [PATCH] feat: add LetterImage model --- backend/.gitignore | 1 + .../letters/migrations/0006_letterimage.py | 29 +++++++++++++++++++ backend/letters/models.py | 10 +++++++ backend/letters/tests.py | 28 ++++++++++++++++++ 4 files changed, 68 insertions(+) create mode 100644 backend/letters/migrations/0006_letterimage.py diff --git a/backend/.gitignore b/backend/.gitignore index 494c7c3..c43be96 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -9,3 +9,4 @@ __pycache__/ *.pyd docs/ +encrypted-images/ diff --git a/backend/letters/migrations/0006_letterimage.py b/backend/letters/migrations/0006_letterimage.py new file mode 100644 index 0000000..1ef49e3 --- /dev/null +++ b/backend/letters/migrations/0006_letterimage.py @@ -0,0 +1,29 @@ +# Generated by Django 6.0.4 on 2026-04-11 20:17 + +import uuid + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("letters", "0005_letter_encrypted_dek"), + ] + + operations = [ + migrations.CreateModel( + name="LetterImage", + fields=[ + ("public_id", models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ("file_name", models.CharField(max_length=255)), + ("file", models.FileField(upload_to="encrypted-images/")), + ( + "letter", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, related_name="images", to="letters.letter" + ), + ), + ], + ), + ] diff --git a/backend/letters/models.py b/backend/letters/models.py index 9b3d98e..1a55666 100644 --- a/backend/letters/models.py +++ b/backend/letters/models.py @@ -38,3 +38,13 @@ class Letter(models.Model): def __str__(self): return f"{self.type} - {self.status}" + + +class LetterImage(models.Model): + public_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + letter = models.ForeignKey(Letter, on_delete=models.CASCADE, related_name="images") + file_name = models.CharField(max_length=255) + file = models.FileField(upload_to="encrypted-images/") + + def __str__(self): + return f"Image {self.public_id} for {self.letter}" diff --git a/backend/letters/tests.py b/backend/letters/tests.py index bff4e14..0a7f8c5 100644 --- a/backend/letters/tests.py +++ b/backend/letters/tests.py @@ -1,7 +1,10 @@ from django.contrib.auth import get_user_model +from django.core.files.base import ContentFile from django.test import TestCase from rest_framework.test import APITestCase +from letters.models import LetterImage + from .models import Letter User = get_user_model() @@ -75,3 +78,28 @@ class LetterAPITest(APITestCase): response.data["non_field_errors"], ["encrypted_dek is required when encrypted_content and encrypted_metadata are present"], ) + + +class LetterImageModelTest(TestCase): + def setUp(self): + self.user = User.objects.create_user(email="img_test@pi-ku.app", password="password1234") + self.letter = Letter.objects.create(user=self.user, type="KEPT", status="DRAFT") + + def test_create_letter_image(self): + """Test images can be associated with a letter (many to 1)""" + image_content = ContentFile(b"fake-encrypted-data", name="test_image.bin") + letter_image = LetterImage.objects.create( + letter=self.letter, file_name="encrypted_image.enc", file=image_content + ) + self.assertEqual(letter_image.letter, self.letter) + self.assertTrue(letter_image.file.name.startswith("encrypted-images/")) + self.assertIsNotNone(letter_image.public_id) + + def test_letter_cascade_deletes_images(self): + """TTest when a letter is deleted, its encrypted images are also removed""" + LetterImage.objects.create( + letter=self.letter, file_name="will_be_deleted.jpg", file=ContentFile(b"data", name="del.bin") + ) + self.assertEqual(LetterImage.objects.count(), 1) + self.letter.delete() + self.assertEqual(LetterImage.objects.count(), 0)