diff --git a/backend/server/adventures/admin.py b/backend/server/adventures/admin.py index 1beac0f..be1793b 100644 --- a/backend/server/adventures/admin.py +++ b/backend/server/adventures/admin.py @@ -8,8 +8,6 @@ from allauth.account.decorators import secure_admin_login admin.autodiscover() admin.site.login = secure_admin_login(admin.site.login) - - class AdventureAdmin(admin.ModelAdmin): list_display = ('name', 'get_category', 'get_visit_count', 'user_id', 'is_public') list_filter = ( 'user_id', 'is_public') diff --git a/backend/server/integrations/__init__.py b/backend/server/integrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/server/integrations/admin.py b/backend/server/integrations/admin.py new file mode 100644 index 0000000..d561cf4 --- /dev/null +++ b/backend/server/integrations/admin.py @@ -0,0 +1,9 @@ +from django.contrib import admin +from allauth.account.decorators import secure_admin_login + +from .models import ImmichIntegration + +admin.autodiscover() +admin.site.login = secure_admin_login(admin.site.login) + +admin.site.register(ImmichIntegration) \ No newline at end of file diff --git a/backend/server/integrations/apps.py b/backend/server/integrations/apps.py new file mode 100644 index 0000000..73adb7a --- /dev/null +++ b/backend/server/integrations/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class IntegrationsConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'integrations' diff --git a/backend/server/integrations/migrations/0001_initial.py b/backend/server/integrations/migrations/0001_initial.py new file mode 100644 index 0000000..73015d1 --- /dev/null +++ b/backend/server/integrations/migrations/0001_initial.py @@ -0,0 +1,26 @@ +# Generated by Django 5.0.8 on 2024-12-31 15:02 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='ImmichIntegration', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('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/__init__.py b/backend/server/integrations/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/server/integrations/models.py b/backend/server/integrations/models.py new file mode 100644 index 0000000..d3394cc --- /dev/null +++ b/backend/server/integrations/models.py @@ -0,0 +1,12 @@ +from django.db import models +from django.contrib.auth import get_user_model + +User = get_user_model() + +class ImmichIntegration(models.Model): + server_url = models.CharField(max_length=255) + api_key = models.CharField(max_length=255) + user = models.ForeignKey(User, on_delete=models.CASCADE) + + def __str__(self): + return self.user.username + ' - ' + self.server_url \ No newline at end of file diff --git a/backend/server/integrations/tests.py b/backend/server/integrations/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/backend/server/integrations/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/backend/server/integrations/urls.py b/backend/server/integrations/urls.py new file mode 100644 index 0000000..cd1cbfa --- /dev/null +++ b/backend/server/integrations/urls.py @@ -0,0 +1,12 @@ +from django.urls import path, include +from rest_framework.routers import DefaultRouter +from integrations.views import ImmichIntegrationView + +# Create the router and register the ViewSet +router = DefaultRouter() +router.register(r'immich', ImmichIntegrationView, basename='immich') + +# Include the router URLs +urlpatterns = [ + path("", include(router.urls)), # Includes /immich/ routes +] diff --git a/backend/server/integrations/views.py b/backend/server/integrations/views.py new file mode 100644 index 0000000..2622df9 --- /dev/null +++ b/backend/server/integrations/views.py @@ -0,0 +1,77 @@ +from rest_framework.response import Response +from rest_framework import viewsets, status +from .models import ImmichIntegration +from rest_framework.decorators import action +import requests + +class ImmichIntegrationView(viewsets.ViewSet): + def check_integration(self, request): + """ + Checks if the user has an active Immich integration. + Returns: + - None if the integration exists. + - A Response with an error message if the integration is missing. + """ + user_integrations = ImmichIntegration.objects.filter(user=request.user) + if not user_integrations.exists(): + return Response( + { + 'message': 'You need to have an active Immich integration to use this feature.', + 'error': True, + 'code': 'immich.integration_missing' + }, + status=status.HTTP_403_FORBIDDEN + ) + return ImmichIntegration.objects.first() + + @action(detail=False, methods=['get'], url_path='search') + def search(self, request): + """ + Handles the logic for searching Immich images. + """ + # Check for integration before proceeding + integration = self.check_integration(request) + if isinstance(integration, Response): + return integration + + query = request.query_params.get('query', '') + + if not query: + return Response( + { + 'message': 'Query is required.', + 'error': True, + 'code': 'immich.query_required' + }, + status=status.HTTP_400_BAD_REQUEST + ) + + + immich_fetch = requests.post(f'{integration.server_url}/search/smart', headers={ + 'x-api-key': integration.api_key + }, + json = { + 'query': query + } + ) + res = immich_fetch.json() + + if 'assets' in res and 'items' in res['assets']: + return Response(res['assets']['items'], status=status.HTTP_200_OK) + else: + return Response( + { + 'message': 'No items found.', + 'error': True, + 'code': 'immich.no_items_found' + }, + status=status.HTTP_404_NOT_FOUND + ) + + + + def get(self, request): + """ + RESTful GET method for searching Immich images. + """ + return self.search(request) diff --git a/backend/server/main/settings.py b/backend/server/main/settings.py index 7e4973b..3882c47 100644 --- a/backend/server/main/settings.py +++ b/backend/server/main/settings.py @@ -56,6 +56,7 @@ INSTALLED_APPS = ( 'adventures', 'worldtravel', 'users', + 'integrations', 'django.contrib.gis', ) diff --git a/backend/server/main/urls.py b/backend/server/main/urls.py index 3e3c53f..ab1e084 100644 --- a/backend/server/main/urls.py +++ b/backend/server/main/urls.py @@ -39,6 +39,8 @@ urlpatterns = [ # path('auth/account-confirm-email/', VerifyEmailView.as_view(), name='account_email_verification_sent'), path("accounts/", include("allauth.urls")), + path("api/integrations/", include("integrations.urls")), + # Include the API endpoints: ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)