feat: ship MVP itinerary optimization, weather, AI key prefs, and MCP tools

This commit is contained in:
2026-03-08 13:49:32 +00:00
parent 9eb0325c7a
commit 8c0637c518
25 changed files with 1888 additions and 511 deletions

View File

@@ -1,4 +1,6 @@
from .immich_view import ImmichIntegrationView, ImmichIntegrationViewSet
from .integration_view import IntegrationView
from .strava_view import StravaIntegrationView
from .wanderer_view import WandererIntegrationViewSet
from .wanderer_view import WandererIntegrationViewSet
from .user_api_key_view import UserAPIKeyViewSet
from .recommendation_profile_view import UserRecommendationPreferenceProfileViewSet

View File

@@ -3,40 +3,77 @@ from rest_framework.response import Response
from rest_framework import viewsets, status
from rest_framework.permissions import IsAuthenticated
from django.utils import timezone
from integrations.models import ImmichIntegration, StravaToken, WandererIntegration
from integrations.models import (
EncryptionConfigurationError,
ImmichIntegration,
StravaToken,
WandererIntegration,
UserAPIKey,
get_field_fernet,
)
from django.conf import settings
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)
google_map_integration = settings.GOOGLE_MAPS_API_KEY != ''
strava_integration_global = settings.STRAVA_CLIENT_ID != '' and settings.STRAVA_CLIENT_SECRET != ''
google_map_integration = (
settings.GOOGLE_MAPS_API_KEY != ""
or UserAPIKey.objects.filter(
user=request.user,
provider="google_maps",
).exists()
)
strava_integration_global = (
settings.STRAVA_CLIENT_ID != "" and settings.STRAVA_CLIENT_SECRET != ""
)
strava_integration_user = StravaToken.objects.filter(user=request.user).exists()
wanderer_integration = WandererIntegration.objects.filter(user=request.user).exists()
wanderer_integration = WandererIntegration.objects.filter(
user=request.user
).exists()
is_wanderer_expired = False
if wanderer_integration:
token_expiry = WandererIntegration.objects.filter(user=request.user).first().token_expiry
token_expiry = (
WandererIntegration.objects.filter(user=request.user)
.first()
.token_expiry
)
if token_expiry and token_expiry < timezone.now():
is_wanderer_expired = True
api_key_status = {
"enabled": UserAPIKey.objects.filter(user=request.user).exists(),
"available": True,
"error": None,
}
try:
get_field_fernet()
except EncryptionConfigurationError as exc:
api_key_status = {
"enabled": False,
"available": False,
"error": str(exc),
}
return Response(
{
'immich': immich_integrations.exists(),
'google_maps': google_map_integration,
'strava': {
'global': strava_integration_global,
'user': strava_integration_user
"immich": immich_integrations.exists(),
"google_maps": google_map_integration,
"api_keys": api_key_status,
"strava": {
"global": strava_integration_global,
"user": strava_integration_user,
},
"wanderer": {
"exists": wanderer_integration,
"expired": is_wanderer_expired,
},
'wanderer': {
'exists': wanderer_integration,
'expired': is_wanderer_expired
}
},
status=status.HTTP_200_OK
status=status.HTTP_200_OK,
)

View File

@@ -0,0 +1,43 @@
from rest_framework import status, viewsets
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from integrations.models import UserRecommendationPreferenceProfile
from integrations.serializers import UserRecommendationPreferenceProfileSerializer
class UserRecommendationPreferenceProfileViewSet(viewsets.ModelViewSet):
serializer_class = UserRecommendationPreferenceProfileSerializer
permission_classes = [IsAuthenticated]
def get_queryset(self):
return UserRecommendationPreferenceProfile.objects.filter(
user=self.request.user
)
def list(self, request, *args, **kwargs):
instance = self.get_queryset().first()
if not instance:
return Response([], status=status.HTTP_200_OK)
serializer = self.get_serializer(instance)
return Response([serializer.data], status=status.HTTP_200_OK)
def perform_create(self, serializer):
existing = UserRecommendationPreferenceProfile.objects.filter(
user=self.request.user
).first()
if existing:
for field, value in serializer.validated_data.items():
setattr(existing, field, value)
existing.save()
self._upserted_instance = existing
return
self._upserted_instance = serializer.save(user=self.request.user)
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
output = self.get_serializer(self._upserted_instance)
return Response(output.data, status=status.HTTP_200_OK)

View File

@@ -0,0 +1,33 @@
from rest_framework import viewsets
from rest_framework.exceptions import APIException
from rest_framework.permissions import IsAuthenticated
from integrations.models import (
EncryptionConfigurationError,
UserAPIKey,
get_field_fernet,
)
from integrations.serializers import UserAPIKeySerializer
class APIKeyConfigurationError(APIException):
status_code = 503
default_detail = (
"API key storage is unavailable due to server encryption configuration."
)
default_code = "api_key_encryption_unavailable"
class UserAPIKeyViewSet(viewsets.ModelViewSet):
serializer_class = UserAPIKeySerializer
permission_classes = [IsAuthenticated]
def initial(self, request, *args, **kwargs):
try:
get_field_fernet()
except EncryptionConfigurationError as exc:
raise APIKeyConfigurationError(detail=str(exc)) from exc
return super().initial(request, *args, **kwargs)
def get_queryset(self):
return UserAPIKey.objects.filter(user=self.request.user).order_by("provider")