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')}
-
-
+
{$t('adventures.url')}
@@ -963,7 +1019,7 @@ it would also work to just use on:click on the MapLibre component itself. -->
-
+
{$t('adventures.wikipedia')}
@@ -981,6 +1037,49 @@ it would also work to just use on:click on the MapLibre component itself. -->
+ {#if immichIntegration}
+
+
+ {$t('immich.immich')}
+
+
+
+
+
+
+
+
{immichError}
+
+ {#each immichImages as image}
+
+

+
+
+ {/each}
+
+
+ {/if}
+
{#if images.length > 0}
diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json
index d847826..8639806 100644
--- a/frontend/src/locales/en.json
+++ b/frontend/src/locales/en.json
@@ -500,5 +500,14 @@
"recent_adventures": "Recent Adventures",
"no_recent_adventures": "No recent adventures?",
"add_some": "Why not start planning your next adventure? You can add a new adventure by clicking the button below."
+ },
+ "immich": {
+ "immich": "Immich",
+ "integration_fetch_error": "Error fetching data from the Immich integration",
+ "integration_missing": "The Immich integration is missing from the backend",
+ "query_required": "Query is required",
+ "server_down": "The Immich server is currently down or unreachable",
+ "no_items_found": "No items found",
+ "imageid_required": "Image ID is required"
}
}
diff --git a/frontend/src/routes/immich/[key]/+server.ts b/frontend/src/routes/immich/[key]/+server.ts
new file mode 100644
index 0000000..33d33de
--- /dev/null
+++ b/frontend/src/routes/immich/[key]/+server.ts
@@ -0,0 +1,54 @@
+import type { RequestHandler } from './$types';
+
+const PUBLIC_SERVER_URL = process.env['PUBLIC_SERVER_URL'];
+const endpoint = PUBLIC_SERVER_URL || 'http://localhost:8000';
+
+export const GET: RequestHandler = async (event) => {
+ try {
+ const key = event.params.key;
+
+ // Forward the session ID from cookies
+ const sessionid = event.cookies.get('sessionid');
+ if (!sessionid) {
+ return new Response(JSON.stringify({ error: 'Session ID is missing' }), {
+ status: 401,
+ headers: { 'Content-Type': 'application/json' }
+ });
+ }
+
+ // Proxy the request to the backend
+ const res = await fetch(`${endpoint}/api/integrations/immich/get/${key}`, {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json',
+ Cookie: `sessionid=${sessionid}`
+ }
+ });
+
+ if (!res.ok) {
+ // Return an error response if the backend request fails
+ const errorData = await res.json();
+ return new Response(JSON.stringify(errorData), {
+ status: res.status,
+ headers: { 'Content-Type': 'application/json' }
+ });
+ }
+
+ // Get the image as a Blob
+ const image = await res.blob();
+
+ // Create a Response to pass the image back
+ return new Response(image, {
+ status: res.status,
+ headers: {
+ 'Content-Type': res.headers.get('Content-Type') || 'image/jpeg'
+ }
+ });
+ } catch (error) {
+ console.error('Error proxying request:', error);
+ return new Response(JSON.stringify({ error: 'Failed to fetch image' }), {
+ status: 500,
+ headers: { 'Content-Type': 'application/json' }
+ });
+ }
+};