diff --git a/backend/core/analysis/migrations/0019_remove_dockerprocess_file_to_analyse_and_more.py b/backend/core/analysis/migrations/0019_remove_dockerprocess_file_to_analyse_and_more.py new file mode 100644 index 00000000..eb9822a2 --- /dev/null +++ b/backend/core/analysis/migrations/0019_remove_dockerprocess_file_to_analyse_and_more.py @@ -0,0 +1,27 @@ +# Generated by Django 4.2.2 on 2023-10-12 10:44 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('core_analysis', '0018_alter_dockerprocess_resources'), + ] + + operations = [ + migrations.RemoveField( + model_name='dockerprocess', + name='file_to_analyse', + ), + migrations.RemoveField( + model_name='dockerprocess', + name='owner', + ), + migrations.DeleteModel( + name='Analysis', + ), + migrations.DeleteModel( + name='DockerProcess', + ), + ] diff --git a/backend/core/fileupload/migrations/0012_alter_license_label.py b/backend/core/fileupload/migrations/0012_alter_license_label.py new file mode 100644 index 00000000..23f87a20 --- /dev/null +++ b/backend/core/fileupload/migrations/0012_alter_license_label.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.2 on 2023-10-19 12:50 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core_fileupload', '0011_analysisresult'), + ] + + operations = [ + migrations.AlterField( + model_name='license', + name='label', + field=models.TextField(default='CC BY - SA 4.0 DEED'), + ), + ] diff --git a/backend/core/fileupload/migrations/0012_file_core_fileup_owner_i_4178bf_idx_and_more.py b/backend/core/fileupload/migrations/0012_file_core_fileup_owner_i_4178bf_idx_and_more.py new file mode 100644 index 00000000..6ba84be9 --- /dev/null +++ b/backend/core/fileupload/migrations/0012_file_core_fileup_owner_i_4178bf_idx_and_more.py @@ -0,0 +1,25 @@ +# Generated by Django 4.2.2 on 2023-10-12 10:44 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core_fileupload', '0011_analysisresult'), + ] + + operations = [ + migrations.AddIndex( + model_name='file', + index=models.Index(fields=['owner', 'confirmation_token'], name='core_fileup_owner_i_4178bf_idx'), + ), + migrations.AddIndex( + model_name='file', + index=models.Index(fields=['confirmation_token'], name='core_fileup_confirm_22c787_idx'), + ), + migrations.AddIndex( + model_name='file', + index=models.Index(fields=['family', 'version', 'is_confirmed'], name='core_fileup_family__df0347_idx'), + ), + ] diff --git a/backend/core/fileupload/migrations/0013_family_core_fileup_owner_i_4e6325_idx_and_more.py b/backend/core/fileupload/migrations/0013_family_core_fileup_owner_i_4e6325_idx_and_more.py new file mode 100644 index 00000000..5b1fcedd --- /dev/null +++ b/backend/core/fileupload/migrations/0013_family_core_fileup_owner_i_4e6325_idx_and_more.py @@ -0,0 +1,41 @@ +# Generated by Django 4.2.2 on 2023-10-12 10:50 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core_fileupload', '0012_file_core_fileup_owner_i_4178bf_idx_and_more'), + ] + + operations = [ + migrations.AddIndex( + model_name='family', + index=models.Index(fields=['owner'], name='core_fileup_owner_i_4e6325_idx'), + ), + migrations.AddIndex( + model_name='file', + index=models.Index(fields=['owner'], name='core_fileup_owner_i_3c733f_idx'), + ), + migrations.AddIndex( + model_name='file', + index=models.Index(fields=['family'], name='core_fileup_family__57b383_idx'), + ), + migrations.AddIndex( + model_name='file', + index=models.Index(fields=['version'], name='core_fileup_version_b47e16_idx'), + ), + migrations.AddIndex( + model_name='file', + index=models.Index(fields=['is_confirmed'], name='core_fileup_is_conf_ba01cb_idx'), + ), + migrations.AddIndex( + model_name='license', + index=models.Index(fields=['label'], name='core_fileup_label_70ea9c_idx'), + ), + migrations.AddIndex( + model_name='tag', + index=models.Index(fields=['label'], name='core_fileup_label_fa57bd_idx'), + ), + ] diff --git a/backend/core/fileupload/migrations/0013_file_private_alter_file_family_alter_file_version.py b/backend/core/fileupload/migrations/0013_file_private_alter_file_family_alter_file_version.py new file mode 100644 index 00000000..26bbacdf --- /dev/null +++ b/backend/core/fileupload/migrations/0013_file_private_alter_file_family_alter_file_version.py @@ -0,0 +1,24 @@ +# Generated by Django 4.2.2 on 2023-10-23 13:08 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('core_fileupload', '0012_alter_license_label'), + ] + + operations = [ + migrations.AlterField( + model_name='file', + name='family', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='core_fileupload.family'), + ), + migrations.AlterField( + model_name='file', + name='version', + field=models.CharField(blank=True, max_length=16, null=True), + ), + ] diff --git a/backend/core/fileupload/migrations/0014_file_private.py b/backend/core/fileupload/migrations/0014_file_private.py new file mode 100644 index 00000000..bb4070f8 --- /dev/null +++ b/backend/core/fileupload/migrations/0014_file_private.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.2 on 2023-10-26 07:29 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core_fileupload', '0013_file_private_alter_file_family_alter_file_version'), + ] + + operations = [ + migrations.AddField( + model_name='file', + name='private', + field=models.BooleanField(default=False), + ), + ] diff --git a/backend/core/fileupload/migrations/0014_remove_file_core_fileup_owner_i_4178bf_idx_and_more.py b/backend/core/fileupload/migrations/0014_remove_file_core_fileup_owner_i_4178bf_idx_and_more.py new file mode 100644 index 00000000..afc34980 --- /dev/null +++ b/backend/core/fileupload/migrations/0014_remove_file_core_fileup_owner_i_4178bf_idx_and_more.py @@ -0,0 +1,29 @@ +# Generated by Django 4.2.2 on 2023-10-12 11:02 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('core_fileupload', '0013_family_core_fileup_owner_i_4e6325_idx_and_more'), + ] + + operations = [ + migrations.RemoveIndex( + model_name='file', + name='core_fileup_owner_i_4178bf_idx', + ), + migrations.RemoveIndex( + model_name='file', + name='core_fileup_confirm_22c787_idx', + ), + migrations.RemoveIndex( + model_name='file', + name='core_fileup_owner_i_3c733f_idx', + ), + migrations.RemoveIndex( + model_name='file', + name='core_fileup_version_b47e16_idx', + ), + ] diff --git a/backend/core/fileupload/migrations/0015_remove_file_core_fileup_family__df0347_idx_and_more.py b/backend/core/fileupload/migrations/0015_remove_file_core_fileup_family__df0347_idx_and_more.py new file mode 100644 index 00000000..061bca05 --- /dev/null +++ b/backend/core/fileupload/migrations/0015_remove_file_core_fileup_family__df0347_idx_and_more.py @@ -0,0 +1,29 @@ +# Generated by Django 4.2.2 on 2023-10-13 06:50 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core_fileupload', '0014_remove_file_core_fileup_owner_i_4178bf_idx_and_more'), + ] + + operations = [ + migrations.RemoveIndex( + model_name='file', + name='core_fileup_family__df0347_idx', + ), + migrations.RemoveIndex( + model_name='license', + name='core_fileup_label_70ea9c_idx', + ), + migrations.RemoveIndex( + model_name='tag', + name='core_fileup_label_fa57bd_idx', + ), + migrations.AddIndex( + model_name='file', + index=models.Index(fields=['owner'], name='core_fileup_owner_i_3c733f_idx'), + ), + ] diff --git a/backend/core/fileupload/migrations/0016_merge_20231113_0937.py b/backend/core/fileupload/migrations/0016_merge_20231113_0937.py new file mode 100644 index 00000000..7d5eaefb --- /dev/null +++ b/backend/core/fileupload/migrations/0016_merge_20231113_0937.py @@ -0,0 +1,14 @@ +# Generated by Django 4.2.2 on 2023-11-13 08:37 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('core_fileupload', '0014_file_private'), + ('core_fileupload', '0015_remove_file_core_fileup_family__df0347_idx_and_more'), + ] + + operations = [ + ] diff --git a/backend/core/fileupload/models.py b/backend/core/fileupload/models.py index 3816bc31..4be0e54e 100644 --- a/backend/core/fileupload/models.py +++ b/backend/core/fileupload/models.py @@ -1,4 +1,7 @@ from django.db import models +from django.db.models.signals import post_migrate +from django.dispatch import receiver + from core.user.models import User from django.core.files.base import ContentFile from django.template.defaultfilters import slugify # new @@ -20,6 +23,9 @@ class Family(models.Model): class Meta: verbose_name = "family" verbose_name_plural = "families" + indexes = [ + models.Index(fields=['owner']), + ] def __str__(self): # do not change that @@ -67,6 +73,13 @@ def create(self, **kwargs): lic.save() return lic + def create_default_license(self): + """ + Creates the default license. + """ + default_license_label = "CC BY - SA 4.0 DEED" + default_license, created = self.get_or_create(label=default_license_label) + return default_license class License(models.Model): """ @@ -74,7 +87,7 @@ class License(models.Model): """ objects = LicenseManager() - _default_license = "CC BY - Mention" + _default_license = "CC BY - SA 4.0 DEED" label = models.TextField(blank=False, default=_default_license) @@ -82,6 +95,13 @@ def __str__(self): # do not change that return f"{self.id}" + @receiver(post_migrate) + def create_default_license(sender, **kwargs): + """ + Creates the default license after migrations. + """ + License.objects.create_default_license() + # -------------------------------------------------- File Model -------------------------------------------------- class FileManager(models.Manager): @@ -101,13 +121,14 @@ def save_file(self, local_file, **kwargs): if tags is None: raise TypeError("Tags is not set") family = kwargs.get("family", None) - # get file from id - if kwargs.get("version", None) is None: - raise TypeError("Version is not set") + version = kwargs.get("version", None) + private = kwargs.get("private", None) # get license from id - if kwargs.get("license", None) is None: + if kwargs.get("license", None) is None and kwargs.get("private") is None: raise TypeError("License not set!") + file = self.model(**kwargs) + file.save() file.tags.set(tags) file.save() @@ -140,14 +161,14 @@ class File(models.Model): relative_upload_dir = "files/" owner = models.ForeignKey(User, on_delete=models.RESTRICT) - family = models.ForeignKey(Family, on_delete=models.CASCADE) + family = models.ForeignKey(Family, on_delete=models.CASCADE, null=True, blank=True) label = models.CharField(blank=False, max_length=255) description = models.TextField(blank=True) local_file = models.FileField(upload_to=relative_upload_dir) uploaded_at = models.DateTimeField(auto_now_add=True) license = models.ForeignKey(License, on_delete=models.CASCADE) tags = models.ManyToManyField(Tag) - version = models.CharField(blank=False, null=False, max_length=16) + version = models.CharField(blank=True, null=True, max_length=16) transpiled_file = models.FileField( null=True, blank=True, upload_to=relative_upload_dir ) @@ -159,6 +180,23 @@ class File(models.Model): ) # indicates if the user confirmed the upload slug = models.SlugField(null=True) confirmation_token = models.CharField(default="", max_length=255) + private = models.BooleanField( + default=False + ) + + class Meta: + indexes = [ + models.Index(fields=['owner']), + models.Index(fields=['family']), + models.Index(fields=['is_confirmed']), + ] + + class Meta: + indexes = [ + models.Index(fields=['owner']), + models.Index(fields=['family']), + models.Index(fields=['is_confirmed']), + ] def __str__(self): # do not change that @@ -169,17 +207,19 @@ def save(self, *args, **kwargs): # new self.slug = slugify(self.label) return super().save(*args, **kwargs) + class Analysis(models.Model): admin_only = models.BooleanField(default=False) disabled = models.BooleanField(default=False) query = models.TextField() - + depends_on = models.ManyToManyField("self", symmetrical=False) + class AnalysisResult(models.Model): triggered = models.BooleanField(default=False) error = models.BooleanField(default=False) result = models.JSONField(null=True) - + analysis = models.ForeignKey(Analysis, on_delete=models.CASCADE) file = models.ForeignKey(File, on_delete=models.CASCADE) diff --git a/backend/core/fileupload/serializers.py b/backend/core/fileupload/serializers.py index 091a98bb..d74d4247 100644 --- a/backend/core/fileupload/serializers.py +++ b/backend/core/fileupload/serializers.py @@ -8,6 +8,7 @@ from transpiler.g6_transpiler import xml_to_g6 from core.fileupload.utils import generate_random_string + class FamiliesSerializer(serializers.ModelSerializer): """ A serializer for defining which Feature Model Family attributes should be converted to JSON @@ -50,6 +51,7 @@ class FilesSerializer(serializers.ModelSerializer): tags = TagsSerializer(many=True) family = FamiliesSerializer() license = LicensesSerializer() + # version = 'self' class Meta: @@ -62,6 +64,9 @@ def validate(self, data): """ Check that the uploaded file contains valid xml """ + family = data.get('family', None) + if family is None: + data['family'] = None try: contents = "" for line in data['local_file']: @@ -108,7 +113,8 @@ def to_internal_value(self, data): tags = list(map(lambda tag: Tag.objects.get(id=tag), data.getlist('tags'))) - if 'family' in data: + family = None + if 'family' in data and data['family']: family = Family.objects.get(id=data['family']) license = License.objects.get(id=data['license']) @@ -119,6 +125,7 @@ def to_internal_value(self, data): return internal_rep.dict() + class AnalysesSerializer(serializers.ModelSerializer): """ A serializer for defining which Analysis attributes should be converted to JSON @@ -128,6 +135,7 @@ class Meta: model = Analysis fields = ['id', 'query', 'admin_only', 'disabled'] + class AnalysisResultsSerializer(serializers.ModelSerializer): """ A serializer for defining which AnalysisResult attributes should be converted to JSON diff --git a/backend/core/fileupload/tests.py b/backend/core/fileupload/tests.py index 68df92fa..abc39754 100644 --- a/backend/core/fileupload/tests.py +++ b/backend/core/fileupload/tests.py @@ -522,21 +522,21 @@ def test_license_list_logged_in_admin(self): self.client.force_authenticate(self.admin) res = self.client.get("/licenses/") json = res.json() - self.assertEqual(len(json), 2) + self.assertEqual(len(json), 3) def test_license_list_logged_in_user(self): # Licenses are listable when logged in with non-admin user self.client.force_authenticate(self.user) res = self.client.get("/licenses/") json = res.json() - self.assertEqual(len(json), 2) + self.assertEqual(len(json), 3) def test_license_list_logged_out(self): # Licenses are listable when logged out self.client.force_authenticate(None) res = self.client.get("/licenses/") json = res.json() - self.assertEqual(len(json), 2) + self.assertEqual(len(json), 3) def test_license_retrieve_logged_in_admin(self): # License is retrievable when logged in diff --git a/backend/core/fileupload/viewsets.py b/backend/core/fileupload/viewsets.py index 0b6c32ab..a948200b 100644 --- a/backend/core/fileupload/viewsets.py +++ b/backend/core/fileupload/viewsets.py @@ -1,3 +1,7 @@ +from django.core.cache import cache +from django.db.models import Q, Prefetch +from rest_framework.request import Request + from core.fileupload.models import Family, Tag, License, File, Analysis, AnalysisResult from core.fileupload.utils import generate_random_string from core.fileupload.serializers import ( @@ -36,13 +40,15 @@ import core.fileupload.githubmirror.github_manager as gm from multiprocessing import Process from rest_framework.views import APIView -from django.http.request import QueryDict +from django.http.request import QueryDict, HttpRequest import json import os import binascii import zipfile +from ..user.models import User + logger = logging.getLogger(__name__) @@ -69,8 +75,11 @@ def anonymize_file(file, request): tags.append(new_tag) anonymized_file[file_key] = tags elif file_key == "family": - new_family = file_value - new_family.update({"owner": new_family["owner"] == user_email}) + if file_value is not None: + new_family = file_value.copy() + new_family.update({"owner": new_family.get("owner") == user_email}) + else: + new_family = None anonymized_file[file_key] = new_family else: anonymized_file[file_key] = file_value @@ -98,6 +107,19 @@ def get(self, request, token): file_data = [] + django_request = HttpRequest() + django_request.method = 'GET' + django_request.META = request.META + django_request.GET = request.query_params.dict() + + request_with_family = HttpRequest() + request_with_family.method = 'GET' + request_with_family.user = request.user + + request_with_owner = HttpRequest() + request_with_owner.method = 'GET' + request_with_owner.user = request.user + for file in files: if file.is_confirmed: return Response( @@ -105,8 +127,20 @@ def get(self, request, token): ) file.is_confirmed = True file.save() + cache.delete_many([f'confirmed_files_{file.owner}', f'confirmed_files_{file.family.id}']) file_data.append(FilesSerializer(file).data) + request_with_family.GET['family'] = file.family.id + request_with_owner.GET['owner'] = file.owner.id + + cache.delete('confirmed_files_all') + confirmed_file_view = ConfirmedFileViewSet.as_view({'get': 'list'}) + confirmed_file_view(django_request) + ConfirmedFileViewSet.as_view({'get': 'list'})(request_with_family) + ConfirmedFileViewSet.as_view({'get': 'list'})(request_with_owner) + + response_with_family = confirmed_file_view(django_request) + return Response({"files": file_data}, status.HTTP_201_CREATED) @@ -172,8 +206,10 @@ class UploadApiView(APIView): permission_classes = [permissions.IsAuthenticated] def check_family(self, request, family_id): - family = Family.objects.get(pk=family_id) - return family and family.owner == request.user + if family_id: + family = Family.objects.get(pk=family_id) + return family and family.owner == request.user + return True def check_tags(self, request, tag_ids): for tag_id in tag_ids: @@ -198,24 +234,74 @@ def schedule_analysis(self, fs): analysis_result.save() -class BulkUploadApiView(UploadApiView): +class PrivateFileUploadApiView(APIView): + permission_classes = [permissions.IsAuthenticated] + def post(self, request, format=None): if not request.data["files"]: return Response( - {"files": "This field may not be blank."}, - status.HTTP_400_BAD_REQUEST, - ) + {"files": "This field may not be blank."}, + status.HTTP_400_BAD_REQUEST, + ) try: files = json.loads(request.data["files"]) except: return Response( - {"files": "This field must contain JSON."}, - status.HTTP_400_BAD_REQUEST, + {"files": "This field must contain JSON."}, + status.HTTP_400_BAD_REQUEST, + ) + for uploaded_file in files: + label = uploaded_file["label"] + description = uploaded_file["description"] + file = request.FILES[uploaded_file["file"]] + license = uploaded_file["license"] + tags = uploaded_file["tags"] + + if not label or not file: + return Response( + {"message": "Missing required fields"}, + status=status.HTTP_400_BAD_REQUEST, ) + validated_data = QueryDict("", mutable=True) + validated_data["label"] = label + validated_data["description"] = description + validated_data.setlist("tags", tags) + validated_data["local_file"] = file + validated_data["private"] = True + validated_data["license"] = license + + fs = FilesSerializer(data=validated_data) + if fs.is_valid(): + fs.save(owner=request.user) + return Response( + {"message": "File uploaded successfully"}, + status=status.HTTP_201_CREATED, + ) + else: + return Response( + {"message": "Invalid file format"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + +class BulkUploadApiView(UploadApiView): + def post(self, request, format=None): + if not request.data["files"]: + return Response( + {"files": "This field may not be blank."}, + status.HTTP_400_BAD_REQUEST, + ) + try: + files = json.loads(request.data["files"]) + except: + return Response( + {"files": "This field must contain JSON."}, + status.HTTP_400_BAD_REQUEST, + ) + serializers = [] confirmation_token = generate_random_string(30) - for uploaded_file in files: label = uploaded_file["label"] description = uploaded_file["description"] @@ -264,16 +350,16 @@ class ZipUploadApiView(UploadApiView): def post(self, request, format=None): if not request.data["files"]: return Response( - {"files": "This field may not be blank."}, - status.HTTP_400_BAD_REQUEST, - ) + {"files": "This field may not be blank."}, + status.HTTP_400_BAD_REQUEST, + ) try: file_data = json.loads(request.data["files"]) except: return Response( - {"files": "This field must contain JSON."}, - status.HTTP_400_BAD_REQUEST, - ) + {"files": "This field must contain JSON."}, + status.HTTP_400_BAD_REQUEST, + ) label = file_data["label"] description = file_data["description"] @@ -289,7 +375,6 @@ def post(self, request, format=None): serializers = [] confirmation_token = generate_random_string(30) - for (i, uploaded_file) in enumerate(files.infolist()): local_file = SimpleUploadedFile( f"{generate_random_string(20)}.xml", files.read(uploaded_file) @@ -299,7 +384,7 @@ def post(self, request, format=None): validated_data["label"] = label validated_data["description"] = description validated_data["license"] = license - validated_data["version"] = f"{i+1}.0.0" + validated_data["version"] = f"{i + 1}.0.0" validated_data["family"] = family validated_data.setlist("tags", tags) validated_data["local_file"] = local_file @@ -382,18 +467,38 @@ def list(self, request, **kwargs): Replace email address of file owner with True or False, indicating if the user which has sent the request is the owner. """ - queryset = File.objects.filter(is_confirmed=True) - familyId = self.request.query_params.get("family") - if familyId is not None: - queryset = queryset.filter(family__id=familyId).order_by("version") + + family_id = self.request.query_params.get("family") owner = self.request.query_params.get("owner") + if family_id is not None and owner is not None: + cache_key = f"confirmed_files_{family_id}_{owner}" + elif family_id is not None: + cache_key = f"confirmed_files_{family_id}_all" + elif owner is not None: + cache_key = f"confirmed_files_all_{owner}" + else: + cache_key = "confirmed_files_all" + + cached_data = cache.get(cache_key) + if cached_data is not None: + return Response(cached_data) + queryset = File.objects.filter(is_confirmed=True) + filter_conditions = Q(is_confirmed=True) + if family_id is not None: + filter_conditions &= Q(family__id=family_id) if owner is not None: - queryset = queryset.filter(owner=owner) - files = FilesSerializer(queryset, many=True).data - anonymized_files = [] - for file in files: - anonymized_file = anonymize_file(file, request) - anonymized_files.append(anonymized_file) + filter_conditions &= Q(owner=owner) + queryset = (queryset.filter(filter_conditions) + .select_related('license') + .prefetch_related( + Prefetch('tags', queryset=Tag.objects.select_related('owner')), + Prefetch('family', queryset=Family.objects.select_related('owner')), + Prefetch('owner', queryset=User.objects.only('email')) + ) + .order_by("version")) + + anonymized_files = [anonymize_file(FilesSerializer(file).data, request) for file in queryset] + cache.set(cache_key, anonymized_files, 60 * 15) return Response(anonymized_files) def retrieve(self, request, *args, **kwargs): @@ -405,6 +510,29 @@ def retrieve(self, request, *args, **kwargs): return Response(anonymized_file) +class PrivateFileViewSet(viewsets.ModelViewSet): + queryset = File.objects.filter(private=True) + serializer_class = FilesSerializer + permission_classes = [IsAuthenticatedOrReadOnly, IsOwnerOrIsAdminOrReadOnly] + + def list(self, request, *args, **kwargs): + queryset = self.queryset.filter(owner=request.user) + serializer = self.serializer_class(queryset, many=True) + files = serializer.data + anonymized_files = [] + for file in files: + anonymized_file = anonymize_file(file, request) + anonymized_files.append(anonymized_file) + return Response(anonymized_files) + + def retrieve(self, request, *args, **kwargs): + instance = self.get_object() + if instance.owner != request.user: + return Response({"error": "Access denied"}, status=status.HTTP_403_FORBIDDEN) + serializer = self.serializer_class(instance) + return Response(serializer.data) + + class FamiliesViewSet( viewsets.GenericViewSet, mixins.ListModelMixin, @@ -431,11 +559,15 @@ def anonymize_family(self, family, request): return anonymized_family def list(self, request, **kwargs): - queryset = Family.objects.all() + cached_families = cache.get(f"families_cache_{request.user}") + if cached_families is not None: + return Response(cached_families) + queryset = Family.objects.prefetch_related('owner').all() families = FamiliesSerializer(queryset, many=True).data anonymized_families = [] for family in families: anonymized_families.append(self.anonymize_family(family, request)) + cache.set(f"families_cache_{request.user}", anonymized_families, 60 * 15) return Response(anonymized_families) def retrieve(self, request, *args, **kwargs): @@ -446,6 +578,8 @@ def retrieve(self, request, *args, **kwargs): def perform_create(self, serializer): serializer.save(owner=self.request.user) + cache_key = f"families_cache_{self.request.user}" + cache.delete(cache_key) class LicensesViewSet( @@ -502,7 +636,7 @@ def anonymize_tag(self, tag, request): return anonymized_tag def list(self, request, **kwargs): - queryset = Tag.objects.all() + queryset = Tag.objects.prefetch_related('owner').all() tags = TagsSerializer(queryset, many=True).data public_tags = self.remove_private_tags(tags, request) anonymized_tags = [] @@ -521,6 +655,7 @@ def retrieve(self, request, *args, **kwargs): def perform_create(self, serializer): serializer.save(owner=self.request.user) + class AnalysesViewSet( viewsets.GenericViewSet, mixins.CreateModelMixin, @@ -528,7 +663,6 @@ class AnalysesViewSet( mixins.DestroyModelMixin, mixins.ListModelMixin, ): - queryset = Analysis.objects.all() serializer_class = AnalysesSerializer permission_classes = [ @@ -540,12 +674,12 @@ def list(self, request, **kwargs): analyses = AnalysesSerializer(queryset, many=True).data return Response(analyses) + class AnalysisResultsViewSet( viewsets.GenericViewSet, mixins.RetrieveModelMixin, mixins.ListModelMixin, ): - queryset = AnalysisResult.objects.all() serializer_class = AnalysisResultsSerializer diff --git a/backend/core/routers.py b/backend/core/routers.py index 617ce2fc..fb014092 100644 --- a/backend/core/routers.py +++ b/backend/core/routers.py @@ -13,7 +13,7 @@ ConfirmedFileViewSet, UnconfirmedFileViewSet, ConfirmFileUploadApiView, - DeleteFileUploadApiView, + DeleteFileUploadApiView, PrivateFileUploadApiView, PrivateFileViewSet, ) from core.user.viewsets import ActivateUserViewSet, UserInfoApiView from core.auth.viewsets import LoginViewSet, RegistrationViewSet, RefreshViewSet @@ -41,6 +41,7 @@ router.register( r"files/uploaded/confirmed", ConfirmedFileViewSet, basename="confirmed-files" ) +router.register(r'files/uploaded/private', PrivateFileViewSet, basename='private-files') router.register( r"files/uploaded/unconfirmed", UnconfirmedFileViewSet, basename="unconfirmed-files" ) @@ -55,6 +56,7 @@ *router.urls, path("bulk-upload/", BulkUploadApiView.as_view()), path("zip-upload/", ZipUploadApiView.as_view()), + path("private-upload/", PrivateFileUploadApiView.as_view()), re_path(r"files/uploaded/unconfirmed/confirm/(?P[\w\d]+)", ConfirmFileUploadApiView.as_view()), re_path(r"files/uploaded/unconfirmed/delete/(?P[\w\d]+)", DeleteFileUploadApiView.as_view()), path("api-auth/", include("rest_framework.urls")), diff --git a/backend/core/tests.py b/backend/core/tests.py index 720992c6..a64cb030 100644 --- a/backend/core/tests.py +++ b/backend/core/tests.py @@ -438,7 +438,7 @@ def test_add_alter_delete_single_file(self): self.assertEqual(f.description, expected_description) self.assertEqual(f.license.label, self.license_label) self.assertEqual(f.family.label, self.family_label) - self.assertEqual(f.version, '') + self.assertEqual(f.version, None) # how many tags are there ? self.assertEqual(len(f.tags.all()), 0) now = timezone.now() diff --git a/backend/ddueruemweb/settings.py b/backend/ddueruemweb/settings.py index 81568398..79047a5e 100644 --- a/backend/ddueruemweb/settings.py +++ b/backend/ddueruemweb/settings.py @@ -50,7 +50,7 @@ 'rest_framework_simplejwt', 'corsheaders', 'django_seed', - + 'debug_toolbar', 'rest_framework.authtoken', 'dj_rest_auth', 'dj_rest_auth.registration', @@ -59,16 +59,22 @@ 'allauth.socialaccount', 'allauth.socialaccount.providers.github', ] - +INTERNAL_IPS = [ + # ... + "127.0.0.1", + # ... +] MIDDLEWARE = [ + 'debug_toolbar.middleware.DebugToolbarMiddleware', 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'corsheaders.middleware.CorsMiddleware', + ] ROOT_URLCONF = 'ddueruemweb.urls' diff --git a/backend/requirements.txt b/backend/requirements.txt index 7eaf9dd1..53c6d9e0 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -3,11 +3,11 @@ certifi==2023.7.22 cffi==1.15.1 charset-normalizer==3.1.0 coverage==7.2.7 -cryptography==41.0.3 +cryptography==41.0.4 defusedxml==0.7.1 Deprecated==1.2.14 dj-rest-auth==4.0.1 -Django==4.2.3 +Django==4.2.7 django-allauth==0.54.0 django-cors-headers==4.1.0 django-coverage-plugin==3.0.0 @@ -35,6 +35,6 @@ requests-oauthlib==1.3.1 six==1.16.0 sqlparse==0.4.4 toposort==1.10 -urllib3==2.0.3 +urllib3==2.0.7 websocket-client==1.6.0 wrapt==1.15.0 diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 17fc8e16..e5fef72b 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -9,7 +9,7 @@ "version": "0.1.0", "dependencies": { "@types/d3": "^7.4.0", - "axios": "^0.27.2", + "axios": "^1.6.0", "chart.js": "^2.8.0", "core-js": "^3.6.5", "cypress": "^10.3.1", @@ -77,12 +77,13 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", - "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dev": true, "dependencies": { - "@babel/highlight": "^7.22.5" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" }, "engines": { "node": ">=6.9.0" @@ -128,12 +129,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.5.tgz", - "integrity": "sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5", + "@babel/types": "^7.23.0", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -257,22 +258,22 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", - "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", - "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "dependencies": { - "@babel/template": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" @@ -414,9 +415,9 @@ } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.5.tgz", - "integrity": "sha512-thqK5QFghPKWLhAV321lxF95yCg2K3Ob5yw+M3VHWfdia0IkPXUtoLH8x/6Fh486QUvzhb8YOWHChTVen2/PoQ==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, "dependencies": { "@babel/types": "^7.22.5" @@ -435,9 +436,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true, "engines": { "node": ">=6.9.0" @@ -482,13 +483,13 @@ } }, "node_modules/@babel/highlight": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", - "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.22.5", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, "engines": { @@ -496,9 +497,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.5.tgz", - "integrity": "sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "bin": { "parser": "bin/babel-parser.js" }, @@ -1787,33 +1788,33 @@ } }, "node_modules/@babel/template": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", - "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.5.tgz", - "integrity": "sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.22.5", - "@babel/generator": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -1822,13 +1823,13 @@ } }, "node_modules/@babel/types": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", - "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, "engines": { @@ -4328,14 +4329,20 @@ "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" }, "node_modules/axios": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", - "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.0.tgz", + "integrity": "sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg==", "dependencies": { - "follow-redirects": "^1.14.9", - "form-data": "^4.0.0" + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" } }, + "node_modules/axios/node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/babel-loader": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.3.0.tgz", @@ -17545,12 +17552,13 @@ } }, "@babel/code-frame": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", - "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dev": true, "requires": { - "@babel/highlight": "^7.22.5" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" } }, "@babel/compat-data": { @@ -17583,12 +17591,12 @@ } }, "@babel/generator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.5.tgz", - "integrity": "sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dev": true, "requires": { - "@babel/types": "^7.22.5", + "@babel/types": "^7.23.0", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -17681,19 +17689,19 @@ } }, "@babel/helper-environment-visitor": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", - "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true }, "@babel/helper-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", - "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "requires": { - "@babel/template": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" } }, "@babel/helper-hoist-variables": { @@ -17799,9 +17807,9 @@ } }, "@babel/helper-split-export-declaration": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.5.tgz", - "integrity": "sha512-thqK5QFghPKWLhAV321lxF95yCg2K3Ob5yw+M3VHWfdia0IkPXUtoLH8x/6Fh486QUvzhb8YOWHChTVen2/PoQ==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, "requires": { "@babel/types": "^7.22.5" @@ -17814,9 +17822,9 @@ "dev": true }, "@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true }, "@babel/helper-validator-option": { @@ -17849,20 +17857,20 @@ } }, "@babel/highlight": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", - "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.22.5", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.5.tgz", - "integrity": "sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==" + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==" }, "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { "version": "7.22.5", @@ -18720,42 +18728,42 @@ } }, "@babel/template": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", - "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dev": true, "requires": { - "@babel/code-frame": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" } }, "@babel/traverse": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.5.tgz", - "integrity": "sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", "dev": true, "requires": { - "@babel/code-frame": "^7.22.5", - "@babel/generator": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", - "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dev": true, "requires": { "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" } }, @@ -20792,12 +20800,20 @@ "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" }, "axios": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", - "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.0.tgz", + "integrity": "sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg==", "requires": { - "follow-redirects": "^1.14.9", - "form-data": "^4.0.0" + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + }, + "dependencies": { + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + } } }, "babel-loader": { diff --git a/frontend/package.json b/frontend/package.json index 7dd328fb..c177472c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,7 +11,7 @@ }, "dependencies": { "@types/d3": "^7.4.0", - "axios": "^0.27.2", + "axios": "^1.6.0", "chart.js": "^2.8.0", "core-js": "^3.6.5", "cypress": "^10.3.1", diff --git a/frontendVue3/package-lock.json b/frontendVue3/package-lock.json index 6702ca88..e9a7df11 100644 --- a/frontendVue3/package-lock.json +++ b/frontendVue3/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.0", "dependencies": { "@mdi/font": "7.0.96", - "axios": "^1.3.4", + "axios": "^1.6.0", "core-js": "^3.8.3", "d3": "^7.4.4", "d3-flextree": "^2.1.2", @@ -438,9 +438,9 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/axios": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", - "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.0.tgz", + "integrity": "sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg==", "dependencies": { "follow-redirects": "^1.15.0", "form-data": "^4.0.0", @@ -2444,9 +2444,9 @@ } }, "node_modules/postcss": { - "version": "8.4.24", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz", - "integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==", + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "funding": [ { "type": "opencollective", diff --git a/frontendVue3/package.json b/frontendVue3/package.json index 41d3bfd5..19926ff9 100644 --- a/frontendVue3/package.json +++ b/frontendVue3/package.json @@ -10,7 +10,7 @@ }, "dependencies": { "@mdi/font": "7.0.96", - "axios": "^1.3.4", + "axios": "^1.6.0", "chart.js": "^4.4.0", "core-js": "^3.8.3", "d3": "^7.4.4", diff --git a/frontendVue3/src/classes/Constraint/Equivalence.js b/frontendVue3/src/classes/Constraint/Equivalence.js new file mode 100644 index 00000000..146026cb --- /dev/null +++ b/frontendVue3/src/classes/Constraint/Equivalence.js @@ -0,0 +1,7 @@ +import { GroupConstraintItem } from '@/classes/Constraint/GroupConstraintItem'; + +export class Equivalence extends GroupConstraintItem { + constructor(first, second) { + super(first, second, '⇔', 'EQUI', 'eq'); + } +} diff --git a/frontendVue3/src/components/ConstraintAddEditDialog.vue b/frontendVue3/src/components/ConstraintAddEditDialog.vue index db65737e..b689433f 100644 --- a/frontendVue3/src/components/ConstraintAddEditDialog.vue +++ b/frontendVue3/src/components/ConstraintAddEditDialog.vue @@ -41,6 +41,13 @@ >implies + + equi + + - diff --git a/frontendVue3/src/components/Constraints.vue b/frontendVue3/src/components/Constraints.vue index a457ebb2..aa13adf4 100644 --- a/frontendVue3/src/components/Constraints.vue +++ b/frontendVue3/src/components/Constraints.vue @@ -105,10 +105,6 @@ import { AddCommand } from '@/classes/Commands/Constraints/AddCommand'; import { CommandManager } from '@/classes/Commands/CommandManager'; import { EditCommand } from '@/classes/Commands/Constraints/EditCommand'; import { DeleteCommand } from '@/classes/Commands/Constraints/DeleteCommand'; -import * as init from '@/services/FeatureModel/init.service'; -import * as dragAndDrop from '@/services/FeatureModel/dragAndDrop.service'; -import * as view from '@/services/FeatureModel/view.service'; -import * as update from '@/services/FeatureModel/update.service'; export default { name: 'Constraints', diff --git a/frontendVue3/src/components/FeatureModel/FeatureModelTreeToolbar.vue b/frontendVue3/src/components/FeatureModel/FeatureModelTreeToolbar.vue index 11a6083d..2ce58fb9 100644 --- a/frontendVue3/src/components/FeatureModel/FeatureModelTreeToolbar.vue +++ b/frontendVue3/src/components/FeatureModel/FeatureModelTreeToolbar.vue @@ -7,6 +7,7 @@ absolute permanent location="start" + id="feature-model-toolbar" > Undo @@ -235,7 +237,7 @@ Tutorial diff --git a/frontendVue3/src/components/FeatureModelTable.vue b/frontendVue3/src/components/FeatureModelTable.vue index d66e7b2d..afe787db 100644 --- a/frontendVue3/src/components/FeatureModelTable.vue +++ b/frontendVue3/src/components/FeatureModelTable.vue @@ -5,7 +5,7 @@ :loading="props.loading" :headers="headers" :items="filteredItems" - items-per-page="5" + items-per-page="10" :search="search" sort-by.sync="sortBy" sort-desc="sortDesc" @@ -21,6 +21,27 @@ vertical > + + + + + + See local storage + + + Upload from local storage @@ -161,7 +161,7 @@ let formData = reactive({ label: '', description: '', files: null, - license: null, + license: "CC BY - SA 4.0 DEED", family: null, version: '', tags: [], @@ -174,11 +174,6 @@ const showDetails = ref(false); let fileRules = [(v) => !!v || 'File is required']; -let { licenses } = storeToRefs(fileStore); -let licenseRules = [(v) => !!v || 'License is required']; - -let familyRules = [(v) => !!v || 'Family is required']; - let checkboxRules = [(v) => !!v || 'Checkbox must be checked']; diff --git a/frontendVue3/src/components/upload_cards/file_create/PrivateUpload.vue b/frontendVue3/src/components/upload_cards/file_create/PrivateUpload.vue new file mode 100644 index 00000000..fb11d98e --- /dev/null +++ b/frontendVue3/src/components/upload_cards/file_create/PrivateUpload.vue @@ -0,0 +1,96 @@ + + + + + diff --git a/frontendVue3/src/components/upload_cards/file_create/Single.vue b/frontendVue3/src/components/upload_cards/file_create/Single.vue index ae716572..dd000493 100644 --- a/frontendVue3/src/components/upload_cards/file_create/Single.vue +++ b/frontendVue3/src/components/upload_cards/file_create/Single.vue @@ -2,6 +2,20 @@ + + + - - - - - - - + + + + + @@ -67,7 +68,6 @@ item-title="label" item-value="id" :required="true" - :rules="familyRules" variant="outlined" density="comfortable" hint="Add to or create new family" @@ -176,7 +176,7 @@ + + diff --git a/frontendVue3/src/components/upload_cards/file_create/Zip.vue b/frontendVue3/src/components/upload_cards/file_create/Zip.vue index 22c56dab..9cce6ed5 100644 --- a/frontendVue3/src/components/upload_cards/file_create/Zip.vue +++ b/frontendVue3/src/components/upload_cards/file_create/Zip.vue @@ -2,6 +2,20 @@ + + + + - - - - - - + + + + @@ -126,7 +127,7 @@ !!v || 'Label is required']; let descriptionRules = [ - (v) => !!v || 'Description is required', (v) => v.length <= 250 || 'Max 250 characters please', ]; let fileRules = [(v) => !!v || 'File is required']; -let { licenses, tags, myOwnTags } = storeToRefs(fileStore); -let licenseRules = [(v) => !!v || 'License is required']; +let { tags, myOwnTags } = storeToRefs(fileStore); -let familyRules = [(v) => !!v || 'Family is required']; let checkboxRules = [(v) => !!v || 'Checkbox must be checked']; -//let versionRules = [(v) => !!v || 'Version is required']; - let addTagMenu = ref(false); let addFamilyMenu = ref(false); +const redirectToLicensePage = () => { + window.open('https://creativecommons.org/licenses/by-sa/4.0/deed.de', '_blank'); +}; diff --git a/frontendVue3/src/services/booleanExpressionParser.service.js b/frontendVue3/src/services/booleanExpressionParser.service.js index 547029f4..aa950bcf 100644 --- a/frontendVue3/src/services/booleanExpressionParser.service.js +++ b/frontendVue3/src/services/booleanExpressionParser.service.js @@ -3,8 +3,9 @@ import {Implication} from "@/classes/Constraint/Implication"; import {Conjunction} from "@/classes/Constraint/Conjunction"; import {Disjunction} from "@/classes/Constraint/Disjunction"; import {FeatureNodeConstraintItem} from "@/classes/Constraint/FeatureNodeConstraintItem"; +import { Equivalence } from '@/classes/Constraint/Equivalence'; -const operators = ['not', 'implies', 'and', 'or']; +const operators = ['not', 'implies', 'and', 'or', 'equi']; export const operatorPrecedence = {}; operators.forEach((operator, i) => operatorPrecedence[operator] = i); @@ -83,6 +84,8 @@ function convertToConstraintItem(operator, stack) { constraintItem = new Disjunction(first, second); } else if (operator === 'implies') { constraintItem = new Implication(first, second); + } else if (operator === 'equi') { + constraintItem = new Equivalence(first, second); } } @@ -95,4 +98,4 @@ function createFeatureNodeConstraintItem(featureNodeName, allNodes) { throw Error(`FeatureNode '${featureNodeName} cannot be found`); } return new FeatureNodeConstraintItem(foundNode); -} \ No newline at end of file +} diff --git a/frontendVue3/src/services/xmlTranspiler.service.js b/frontendVue3/src/services/xmlTranspiler.service.js index 1dfcffc1..c3263c68 100644 --- a/frontendVue3/src/services/xmlTranspiler.service.js +++ b/frontendVue3/src/services/xmlTranspiler.service.js @@ -6,6 +6,7 @@ import { Conjunction } from '@/classes/Constraint/Conjunction'; import { Implication } from '@/classes/Constraint/Implication'; import { Negation } from '@/classes/Constraint/Negation'; import { SoloDisjunction } from '@/classes/Constraint/SoloDisjunction'; +import { Equivalence } from '@/classes/Constraint/Equivalence'; export function xmlToJson(currentModel, data) { /*const start = performance.now();*/ @@ -93,6 +94,8 @@ function readConstraintItem(item, data) { return new Conjunction(childItems[0], childItems[1]); case 'imp': return new Implication(childItems[0], childItems[1]); + case 'eq': + return new Equivalence(childItems[0], childItems[1]); case 'not': return new Negation(childItems[0]); } diff --git a/frontendVue3/src/store/file.js b/frontendVue3/src/store/file.js index d75097ce..12fec347 100644 --- a/frontendVue3/src/store/file.js +++ b/frontendVue3/src/store/file.js @@ -14,6 +14,8 @@ export const useFileStore = defineStore('file', { confirmedFeatureModels: [], myConfirmedFeatureModels: [], featureModels: [], + defaultLicense: null, + myPrivateFeatureModels:[] }), getters: { myOwnTags(state) { @@ -29,6 +31,11 @@ export const useFileStore = defineStore('file', { this.confirmedFeatureModels = response.data; }); }, + fetchMyPrivateFeatureModels() { + api.get(`${API_URL}files/uploaded/private/`).then((response) => { + this.myPrivateFeatureModels = response.data; + }); + }, async fetchMyConfirmedFeatureModels() { const id = useAuthStore().currentUser.id; api.get(`${API_URL}files/uploaded/confirmed/?owner=` + id).then( @@ -77,6 +84,7 @@ export const useFileStore = defineStore('file', { }, fetchLicenses() { api.get(`${API_URL}licenses/`).then((response) => { + this.defaultLicense = response.data.filter((li) => li.label === "CC BY - SA 4.0 DEED").shift(); this.licenses = response.data; }); }, @@ -140,6 +148,31 @@ export const useFileStore = defineStore('file', { return false; }) }, + async uploadPrivateFile(data) { + const appStore = useAppStore(); + return await api + .post(`${API_URL}private-upload/`, data, { + headers: { 'Content-Type': 'multipart/form-data' }, + }) + .then(() => { + appStore.updateSnackbar( + 'Upload successfully!', + 'success', + 5000, + true + ); + return true + }) + .catch((error) => { + appStore.updateSnackbar( + 'Error! ' + error.message, + 'error', + 5000, + true + ); + return false; + }); + }, async uploadTag(payload) { // payload = { label, description, is_public } const appStore = useAppStore(); diff --git a/frontendVue3/src/views/FeatureModel.vue b/frontendVue3/src/views/FeatureModel.vue index 229d2154..3e5b3dae 100644 --- a/frontendVue3/src/views/FeatureModel.vue +++ b/frontendVue3/src/views/FeatureModel.vue @@ -115,7 +115,11 @@ @continue-editing="continueEditing" > - + + diff --git a/frontendVue3/src/views/FileDetail.vue b/frontendVue3/src/views/FileDetail.vue index 31e967a7..f5432f07 100644 --- a/frontendVue3/src/views/FileDetail.vue +++ b/frontendVue3/src/views/FileDetail.vue @@ -219,9 +219,9 @@
-
+
+ :available-tags="tags"/>
@@ -235,16 +235,15 @@ import {busyBoxConfigs} from "@/assets/busyBoxAnalyzeExample"; import {useDisplay, useTheme} from "vuetify"; import {VSkeletonLoader} from 'vuetify/labs/VSkeletonLoader' import LineChart from '@/components/Charts/LineChart.vue'; - - - - - +import {useFileStore} from "@/store/file"; +import {storeToRefs} from "pinia"; const breakpoints = useDisplay(); const theme = useTheme(); const route = useRoute(); const API_URL = import.meta.env.VITE_APP_DOMAIN; +const fileStore = useFileStore(); +const { tags } = storeToRefs(useFileStore()); const family = ref({}); const files = ref([]); @@ -364,5 +363,6 @@ function onElementHover(elem) { onMounted(async () => { await getFamily(); await fetchFeatureModelOfFamily(); + fileStore.fetchTags(); }); diff --git a/frontendVue3/src/views/Home.vue b/frontendVue3/src/views/Home.vue index 470e38dc..64b963b1 100644 --- a/frontendVue3/src/views/Home.vue +++ b/frontendVue3/src/views/Home.vue @@ -8,36 +8,37 @@ - - + > + mdi-school + + +
diff --git a/frontendVue3/src/views/Models.vue b/frontendVue3/src/views/Models.vue index 38ff0867..e41be55b 100644 --- a/frontendVue3/src/views/Models.vue +++ b/frontendVue3/src/views/Models.vue @@ -5,8 +5,10 @@