diff --git a/backend/server/adventures/admin.py b/backend/server/adventures/admin.py index 5c39301..a1a1101 100644 --- a/backend/server/adventures/admin.py +++ b/backend/server/adventures/admin.py @@ -1,7 +1,7 @@ import os from django.contrib import admin from django.utils.html import mark_safe -from .models import Adventure, Checklist, ChecklistItem, Collection, Transportation, Note, AdventureImage, Visit, Category +from .models import Adventure, Checklist, ChecklistItem, Collection, Transportation, Note, AdventureImage, Visit, Category, Attachment from worldtravel.models import Country, Region, VisitedRegion, City, VisitedCity from allauth.account.decorators import secure_admin_login @@ -139,6 +139,7 @@ admin.site.register(AdventureImage, AdventureImageAdmin) admin.site.register(Category, CategoryAdmin) admin.site.register(City, CityAdmin) admin.site.register(VisitedCity) +admin.site.register(Attachment) admin.site.site_header = 'AdventureLog Admin' admin.site.site_title = 'AdventureLog Admin Site' diff --git a/backend/server/adventures/migrations/0018_attachment.py b/backend/server/adventures/migrations/0018_attachment.py new file mode 100644 index 0000000..f41c44b --- /dev/null +++ b/backend/server/adventures/migrations/0018_attachment.py @@ -0,0 +1,26 @@ +# Generated by Django 5.0.8 on 2025-01-19 00:39 + +import django.db.models.deletion +import uuid +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('adventures', '0017_adventureimage_is_primary'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Attachment', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)), + ('file', models.FileField(upload_to='attachments/')), + ('adventure', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='attachments', to='adventures.adventure')), + ('user_id', models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/backend/server/adventures/models.py b/backend/server/adventures/models.py index 779d7f6..346c509 100644 --- a/backend/server/adventures/models.py +++ b/backend/server/adventures/models.py @@ -287,6 +287,16 @@ class AdventureImage(models.Model): def __str__(self): return self.image.url + +class Attachment(models.Model): + id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True) + user_id = models.ForeignKey( + User, on_delete=models.CASCADE, default=default_user_id) + file = models.FileField(upload_to='attachments/') + adventure = models.ForeignKey(Adventure, related_name='attachments', on_delete=models.CASCADE) + + def __str__(self): + return self.file.url class Category(models.Model): id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True) diff --git a/backend/server/adventures/utils/check_adventure_image_permisison.py b/backend/server/adventures/utils/check_adventure_image_permisison.py deleted file mode 100644 index ad7df57..0000000 --- a/backend/server/adventures/utils/check_adventure_image_permisison.py +++ /dev/null @@ -1,33 +0,0 @@ -from adventures.models import AdventureImage - -def checkAdventureImagePermission(imageId, user): - """ - Checks if the given user has permission to access the specified adventure image. - - Args: - imageId (str): The ID of the image to check permissions for. - user (User): The user object to check permissions against. - - Returns: - bool: True if the user has permission to access the image, False otherwise. - - Raises: - AdventureImage.DoesNotExist: If the image with the specified ID does not exist. - """ - try: - # Construct the full relative path to match the database field - image_path = f"images/{imageId}" - # Fetch the AdventureImage object - adventure = AdventureImage.objects.get(image=image_path).adventure - if adventure.is_public: - return True - elif adventure.user_id == user: - return True - elif adventure.collection: - if adventure.collection.shared_with.filter(id=user.id).exists(): - return True - else: - return False - except AdventureImage.DoesNotExist: - print('No image') - return False diff --git a/backend/server/adventures/utils/file_permissions.py b/backend/server/adventures/utils/file_permissions.py new file mode 100644 index 0000000..02971bc --- /dev/null +++ b/backend/server/adventures/utils/file_permissions.py @@ -0,0 +1,41 @@ +from adventures.models import AdventureImage, Attachment + +protected_paths = ['images/', 'attachments/'] + +def checkFilePermission(fileId, user, mediaType): + if mediaType not in protected_paths: + return True + if mediaType == 'images/': + try: + # Construct the full relative path to match the database field + image_path = f"images/{fileId}" + # Fetch the AdventureImage object + adventure = AdventureImage.objects.get(image=image_path).adventure + if adventure.is_public: + return True + elif adventure.user_id == user: + return True + elif adventure.collection: + if adventure.collection.shared_with.filter(id=user.id).exists(): + return True + else: + return False + except AdventureImage.DoesNotExist: + return False + elif mediaType == 'attachments/': + try: + # Construct the full relative path to match the database field + attachment_path = f"attachments/{fileId}" + # Fetch the Attachment object + attachment = Attachment.objects.get(file=attachment_path).adventure + if attachment.is_public: + return True + elif attachment.user_id == user: + return True + elif attachment.collection: + if attachment.collection.shared_with.filter(id=user.id).exists(): + return True + else: + return False + except Attachment.DoesNotExist: + return False \ No newline at end of file diff --git a/backend/server/main/settings.py b/backend/server/main/settings.py index 83ab09a..d8aa026 100644 --- a/backend/server/main/settings.py +++ b/backend/server/main/settings.py @@ -81,8 +81,6 @@ MIDDLEWARE = ( # disable verifications for new users ACCOUNT_EMAIL_VERIFICATION = 'none' -ALLAUTH_UI_THEME = "night" - CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', diff --git a/backend/server/main/views.py b/backend/server/main/views.py index 594017b..3393e13 100644 --- a/backend/server/main/views.py +++ b/backend/server/main/views.py @@ -3,9 +3,8 @@ from django.middleware.csrf import get_token from os import getenv from django.conf import settings from django.http import HttpResponse, HttpResponseForbidden -from django.contrib.auth.decorators import login_required from django.views.static import serve -from adventures.utils.check_adventure_image_permisison import checkAdventureImagePermission +from adventures.utils.file_permissions import checkFilePermission def get_csrf_token(request): csrf_token = get_token(request) @@ -14,16 +13,19 @@ def get_csrf_token(request): def get_public_url(request): return JsonResponse({'PUBLIC_URL': getenv('PUBLIC_URL')}) +protected_paths = ['images/', 'attachments/'] + def serve_protected_media(request, path): - if path.startswith('images/'): + if any([path.startswith(protected_path) for protected_path in protected_paths]): image_id = path.split('/')[1] user = request.user - if checkAdventureImagePermission(image_id, user): + media_type = path.split('/')[0] + '/' + if checkFilePermission(image_id, user, media_type): if settings.DEBUG: # In debug mode, serve the file directly return serve(request, path, document_root=settings.MEDIA_ROOT) else: - # In production, use X-Accel-Redirect + # In production, use X-Accel-Redirect to serve the file using Nginx response = HttpResponse() response['Content-Type'] = '' response['X-Accel-Redirect'] = '/protectedMedia/' + path @@ -31,8 +33,10 @@ def serve_protected_media(request, path): else: return HttpResponseForbidden() else: - response = HttpResponse() - response['Content-Type'] = '' - response['X-Accel-Redirect'] = '/protectedMedia/' + path - return response - \ No newline at end of file + if settings.DEBUG: + return serve(request, path, document_root=settings.MEDIA_ROOT) + else: + response = HttpResponse() + response['Content-Type'] = '' + response['X-Accel-Redirect'] = '/protectedMedia/' + path + return response \ No newline at end of file