diff --git a/backend/server/integrations/migrations/0001_initial.py b/backend/server/integrations/migrations/0001_initial.py index 73015d1..0b05b2a 100644 --- a/backend/server/integrations/migrations/0001_initial.py +++ b/backend/server/integrations/migrations/0001_initial.py @@ -1,5 +1,6 @@ # Generated by Django 5.0.8 on 2024-12-31 15:02 +import uuid import django.db.models.deletion from django.conf import settings from django.db import migrations, models @@ -17,10 +18,11 @@ class Migration(migrations.Migration): migrations.CreateModel( name='ImmichIntegration', fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)), ('server_url', models.CharField(max_length=255)), ('api_key', models.CharField(max_length=255)), ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], ), ] + diff --git a/backend/server/integrations/migrations/0003_alter_immichintegration_user.py b/backend/server/integrations/migrations/0003_alter_immichintegration_user.py new file mode 100644 index 0000000..30bd443 --- /dev/null +++ b/backend/server/integrations/migrations/0003_alter_immichintegration_user.py @@ -0,0 +1,21 @@ +# Generated by Django 5.0.8 on 2025-01-02 17:50 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('integrations', '0002_alter_immichintegration_user'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AlterField( + model_name='immichintegration', + name='user', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/backend/server/integrations/models.py b/backend/server/integrations/models.py index 0d7a0a4..9db8a07 100644 --- a/backend/server/integrations/models.py +++ b/backend/server/integrations/models.py @@ -1,12 +1,15 @@ from django.db import models from django.contrib.auth import get_user_model +import uuid User = get_user_model() class ImmichIntegration(models.Model): server_url = models.CharField(max_length=255) api_key = models.CharField(max_length=255) - user = models.OneToOneField(User, on_delete=models.CASCADE) + user = models.ForeignKey( + User, on_delete=models.CASCADE) + id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True) def __str__(self): return self.user.username + ' - ' + self.server_url \ No newline at end of file diff --git a/backend/server/integrations/serializers.py b/backend/server/integrations/serializers.py new file mode 100644 index 0000000..cc92d21 --- /dev/null +++ b/backend/server/integrations/serializers.py @@ -0,0 +1,13 @@ +from .models import ImmichIntegration +from rest_framework import serializers + +class ImmichIntegrationSerializer(serializers.ModelSerializer): + class Meta: + model = ImmichIntegration + fields = '__all__' + read_only_fields = ['id', 'user'] + + def to_representation(self, instance): + representation = super().to_representation(instance) + representation.pop('user', None) + return representation diff --git a/backend/server/integrations/urls.py b/backend/server/integrations/urls.py index df405f1..a15bbd0 100644 --- a/backend/server/integrations/urls.py +++ b/backend/server/integrations/urls.py @@ -1,11 +1,12 @@ from django.urls import path, include from rest_framework.routers import DefaultRouter -from integrations.views import ImmichIntegrationView, IntegrationView +from integrations.views import ImmichIntegrationView, IntegrationView, ImmichIntegrationViewSet # Create the router and register the ViewSet router = DefaultRouter() router.register(r'immich', ImmichIntegrationView, basename='immich') router.register(r'', IntegrationView, basename='integrations') +router.register(r'immich', ImmichIntegrationViewSet, basename='immich_viewset') # Include the router URLs urlpatterns = [ diff --git a/backend/server/integrations/views.py b/backend/server/integrations/views.py index 7a05fa3..673fad5 100644 --- a/backend/server/integrations/views.py +++ b/backend/server/integrations/views.py @@ -1,6 +1,8 @@ import os from rest_framework.response import Response from rest_framework import viewsets, status + +from .serializers import ImmichIntegrationSerializer from .models import ImmichIntegration from rest_framework.decorators import action from rest_framework.permissions import IsAuthenticated @@ -148,3 +150,83 @@ class ImmichIntegrationView(viewsets.ViewSet): }, status=status.HTTP_503_SERVICE_UNAVAILABLE ) + +class ImmichIntegrationViewSet(viewsets.ModelViewSet): + permission_classes = [IsAuthenticated] + serializer_class = ImmichIntegrationSerializer + queryset = ImmichIntegration.objects.all() + + def get_queryset(self): + return ImmichIntegration.objects.filter(user=self.request.user) + + def create(self, request): + """ + RESTful POST method for creating a new Immich integration. + """ + + # Check if the user already has an integration + user_integrations = ImmichIntegration.objects.filter(user=request.user) + if user_integrations.exists(): + return Response( + { + 'message': 'You already have an active Immich integration.', + 'error': True, + 'code': 'immich.integration_exists' + }, + status=status.HTTP_400_BAD_REQUEST + ) + + serializer = self.serializer_class(data=request.data) + if serializer.is_valid(): + serializer.save(user=request.user) + return Response( + serializer.data, + status=status.HTTP_201_CREATED + ) + return Response( + serializer.errors, + status=status.HTTP_400_BAD_REQUEST + ) + + def destroy(self, request, pk=None): + """ + RESTful DELETE method for deleting an existing Immich integration. + """ + integration = ImmichIntegration.objects.filter(user=request.user, id=pk).first() + if not integration: + return Response( + { + 'message': 'Integration not found.', + 'error': True, + 'code': 'immich.integration_not_found' + }, + status=status.HTTP_404_NOT_FOUND + ) + integration.delete() + return Response( + { + 'message': 'Integration deleted successfully.' + }, + status=status.HTTP_200_OK + ) + + def list(self, request, *args, **kwargs): + # If the user has an integration, we only want to return that integration + + user_integrations = ImmichIntegration.objects.filter(user=request.user) + if user_integrations.exists(): + integration = user_integrations.first() + serializer = self.serializer_class(integration) + return Response( + serializer.data, + status=status.HTTP_200_OK + ) + else: + return Response( + { + 'message': 'No integration found.', + 'error': True, + 'code': 'immich.integration_not_found' + }, + status=status.HTTP_404_NOT_FOUND + ) \ No newline at end of file diff --git a/frontend/src/lib/components/AdventureCard.svelte b/frontend/src/lib/components/AdventureCard.svelte index 0d903ee..b77b8ea 100644 --- a/frontend/src/lib/components/AdventureCard.svelte +++ b/frontend/src/lib/components/AdventureCard.svelte @@ -191,7 +191,7 @@ {#if type != 'link'} - {#if adventure.user_id == user?.uuid || (collection && user && collection.shared_with.includes(user.uuid))} + {#if adventure.user_id == user?.uuid || (collection && user && collection.shared_with?.includes(user.uuid))}
+ Integrate your Immich account with AdventureLog to allow you to search your photos library + and import photos for your adventures. +
+ {#if immichIntegration} ++ Note: this must be the URL to the Immich API server so it likely ends with /api + unless you have a custom config. +
+ {/if} +