From 67f6af8ca36de1006e78f4efdc0ea4178c45f9dc Mon Sep 17 00:00:00 2001 From: Sean Morley Date: Wed, 1 Jan 2025 16:24:44 -0500 Subject: [PATCH] feat: add Immich integration view and API documentation, enhance error handling, and include SVG asset --- backend/server/integrations/urls.py | 3 +- backend/server/integrations/views.py | 16 ++ backend/server/main/settings.py | 8 - backend/server/templates/base.html | 2 + frontend/src/lib/assets/immich.svg | 1 + .../src/lib/components/AdventureModal.svelte | 151 +++++++++++++++--- frontend/src/locales/en.json | 9 ++ frontend/src/routes/immich/[key]/+server.ts | 54 +++++++ 8 files changed, 209 insertions(+), 35 deletions(-) create mode 100644 frontend/src/lib/assets/immich.svg create mode 100644 frontend/src/routes/immich/[key]/+server.ts diff --git a/backend/server/integrations/urls.py b/backend/server/integrations/urls.py index cd1cbfa..df405f1 100644 --- a/backend/server/integrations/urls.py +++ b/backend/server/integrations/urls.py @@ -1,10 +1,11 @@ from django.urls import path, include from rest_framework.routers import DefaultRouter -from integrations.views import ImmichIntegrationView +from integrations.views import ImmichIntegrationView, IntegrationView # Create the router and register the ViewSet router = DefaultRouter() router.register(r'immich', ImmichIntegrationView, basename='immich') +router.register(r'', IntegrationView, basename='integrations') # Include the router URLs urlpatterns = [ diff --git a/backend/server/integrations/views.py b/backend/server/integrations/views.py index 72c9c4a..7a05fa3 100644 --- a/backend/server/integrations/views.py +++ b/backend/server/integrations/views.py @@ -7,6 +7,22 @@ from rest_framework.permissions import IsAuthenticated import requests from rest_framework.pagination import PageNumberPagination +class IntegrationView(viewsets.ViewSet): + permission_classes = [IsAuthenticated] + def list(self, request): + """ + RESTful GET method for listing all integrations. + """ + immich_integrations = ImmichIntegration.objects.filter(user=request.user) + + return Response( + { + 'immich': immich_integrations.exists() + }, + status=status.HTTP_200_OK + ) + + class StandardResultsSetPagination(PageNumberPagination): page_size = 25 page_size_query_param = 'page_size' diff --git a/backend/server/main/settings.py b/backend/server/main/settings.py index 3882c47..b934b99 100644 --- a/backend/server/main/settings.py +++ b/backend/server/main/settings.py @@ -165,9 +165,6 @@ TEMPLATES = [ DISABLE_REGISTRATION = getenv('DISABLE_REGISTRATION', 'False') == 'True' DISABLE_REGISTRATION_MESSAGE = getenv('DISABLE_REGISTRATION_MESSAGE', 'Registration is disabled. Please contact the administrator if you need an account.') -ALLAUTH_UI_THEME = "dark" -SILENCED_SYSTEM_CHECKS = ["slippers.E001"] - AUTH_USER_MODEL = 'users.CustomUser' ACCOUNT_ADAPTER = 'users.adapters.NoNewUsersAccountAdapter' @@ -223,11 +220,6 @@ REST_FRAMEWORK = { 'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema', } -SWAGGER_SETTINGS = { - 'LOGIN_URL': 'login', - 'LOGOUT_URL': 'logout', -} - CORS_ALLOWED_ORIGINS = [origin.strip() for origin in getenv('CSRF_TRUSTED_ORIGINS', 'http://localhost').split(',') if origin.strip()] diff --git a/backend/server/templates/base.html b/backend/server/templates/base.html index be712b7..9e1d48c 100644 --- a/backend/server/templates/base.html +++ b/backend/server/templates/base.html @@ -53,6 +53,7 @@ >Documentation +
  • Source Code
  • +
  • API Docs
  • diff --git a/frontend/src/lib/assets/immich.svg b/frontend/src/lib/assets/immich.svg new file mode 100644 index 0000000..70aa672 --- /dev/null +++ b/frontend/src/lib/assets/immich.svg @@ -0,0 +1 @@ + diff --git a/frontend/src/lib/components/AdventureModal.svelte b/frontend/src/lib/components/AdventureModal.svelte index cc081a0..f405b5b 100644 --- a/frontend/src/lib/components/AdventureModal.svelte +++ b/frontend/src/lib/components/AdventureModal.svelte @@ -13,7 +13,7 @@ import { addToast } from '$lib/toasts'; import { deserialize } from '$app/forms'; import { t } from 'svelte-i18n'; - + import ImmichLogo from '$lib/assets/immich.svg'; export let longitude: number | null = null; export let latitude: number | null = null; export let collection: Collection | null = null; @@ -180,31 +180,74 @@ } async function fetchImage() { - let res = await fetch(url); - let data = await res.blob(); - if (!data) { - imageError = $t('adventures.no_image_url'); - return; - } - let file = new File([data], 'image.jpg', { type: 'image/jpeg' }); - let formData = new FormData(); - formData.append('image', file); - formData.append('adventure', adventure.id); - let res2 = await fetch(`/adventures?/image`, { - method: 'POST', - body: formData - }); - let data2 = await res2.json(); - console.log(data2); - if (data2.type === 'success') { - images = [...images, data2]; - adventure.images = images; - addToast('success', $t('adventures.image_upload_success')); - } else { + try { + let res = await fetch(url); + let data = await res.blob(); + if (!data) { + imageError = $t('adventures.no_image_url'); + return; + } + let file = new File([data], 'image.jpg', { type: 'image/jpeg' }); + let formData = new FormData(); + formData.append('image', file); + formData.append('adventure', adventure.id); + + let res2 = await fetch(`/adventures?/image`, { + method: 'POST', + body: formData + }); + let data2 = await res2.json(); + + if (data2.type === 'success') { + console.log('Response Data:', data2); + + // Deserialize the nested data + let rawData = JSON.parse(data2.data); // Parse the data field + console.log('Deserialized Data:', rawData); + + // Assuming the first object in the array is the new image + let newImage = { + id: rawData[0].id, + image: rawData[2] // This is the URL for the image + }; + console.log('New Image:', newImage); + + // Update images and adventure + images = [...images, newImage]; + adventure.images = images; + + addToast('success', $t('adventures.image_upload_success')); + } else { + addToast('error', $t('adventures.image_upload_error')); + } + } catch (error) { + console.error('Error in fetchImage:', error); addToast('error', $t('adventures.image_upload_error')); } } + let immichSearchValue: string = ''; + let immichError: string = ''; + + async function searchImmich() { + let res = await fetch(`/api/integrations/immich/search/?query=${immichSearchValue}`); + if (!res.ok) { + let data = await res.json(); + let errorMessage = data.message; + console.log(errorMessage); + immichError = $t(data.code); + } else { + let data = await res.json(); + console.log(data); + immichError = ''; + if (data.results && data.results.length > 0) { + immichImages = data.results; + } else { + immichError = $t('immich.no_items_found'); + } + } + } + async function fetchWikiImage() { let res = await fetch(`/api/generate/img/?name=${imageSearch}`); let data = await res.json(); @@ -337,6 +380,9 @@ const dispatch = createEventDispatcher(); let modal: HTMLDialogElement; + let immichIntegration: boolean = false; + let immichImages: any[] = []; + onMount(async () => { modal = document.getElementById('my_modal_1') as HTMLDialogElement; modal.showModal(); @@ -347,6 +393,16 @@ } else { addToast('error', $t('adventures.category_fetch_error')); } + // Check for Immich Integration + let res = await fetch('/api/integrations'); + if (!res.ok) { + addToast('error', $t('immich.integration_fetch_error')); + } else { + let data = await res.json(); + if (data.immich) { + immichIntegration = true; + } + } }); function close() { @@ -915,10 +971,10 @@ it would also work to just use on:click on the MapLibre component itself. --> {:else} -

    {$t('adventures.upload_images_here')}

    +

    {$t('adventures.upload_images_here')}

    -
    -