mirror of
https://github.com/seanmorley15/AdventureLog.git
synced 2025-08-05 05:05:17 +02:00
feat: enhance Strava integration with user-specific settings and management options; update localization strings
This commit is contained in:
parent
9bcada21dd
commit
1891a8b497
5 changed files with 152 additions and 39 deletions
|
@ -2,7 +2,7 @@ import os
|
|||
from rest_framework.response import Response
|
||||
from rest_framework import viewsets, status
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from integrations.models import ImmichIntegration
|
||||
from integrations.models import ImmichIntegration, StravaToken
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
|
@ -14,13 +14,17 @@ class IntegrationView(viewsets.ViewSet):
|
|||
"""
|
||||
immich_integrations = ImmichIntegration.objects.filter(user=request.user)
|
||||
google_map_integration = settings.GOOGLE_MAPS_API_KEY != ''
|
||||
strava_integration = settings.STRAVA_CLIENT_ID != '' and settings.STRAVA_CLIENT_SECRET != ''
|
||||
strava_integration_global = settings.STRAVA_CLIENT_ID != '' and settings.STRAVA_CLIENT_SECRET != ''
|
||||
strava_integration_user = StravaToken.objects.filter(user=request.user).exists()
|
||||
|
||||
return Response(
|
||||
{
|
||||
'immich': immich_integrations.exists(),
|
||||
'google_maps': google_map_integration,
|
||||
'strava': strava_integration
|
||||
'strava': {
|
||||
'global': strava_integration_global,
|
||||
'user': strava_integration_user
|
||||
}
|
||||
},
|
||||
status=status.HTTP_200_OK
|
||||
)
|
||||
|
|
|
@ -5,6 +5,7 @@ from rest_framework.decorators import action
|
|||
import requests
|
||||
import logging
|
||||
import time
|
||||
from django.shortcuts import redirect
|
||||
from django.conf import settings
|
||||
from integrations.models import StravaToken
|
||||
|
||||
|
@ -87,16 +88,12 @@ class StravaIntegrationView(viewsets.ViewSet):
|
|||
}
|
||||
)
|
||||
|
||||
return Response(
|
||||
{
|
||||
'message': 'Strava access token obtained and saved successfully.',
|
||||
'access_token': response_data.get('access_token'),
|
||||
'expires_at': response_data.get('expires_at'),
|
||||
'refresh_token': response_data.get('refresh_token'),
|
||||
'athlete': response_data.get('athlete'),
|
||||
},
|
||||
status=status.HTTP_200_OK
|
||||
)
|
||||
# redirect to frontend url / setttigns
|
||||
frontend_url = settings.FRONTEND_URL
|
||||
if not frontend_url.endswith('/'):
|
||||
frontend_url += '/'
|
||||
return redirect(f"{frontend_url}settings?strava_authorized=true")
|
||||
|
||||
|
||||
except requests.RequestException as e:
|
||||
logger.error("Error during Strava OAuth token exchange: %s", str(e))
|
||||
|
@ -110,6 +107,28 @@ class StravaIntegrationView(viewsets.ViewSet):
|
|||
status=status.HTTP_502_BAD_GATEWAY
|
||||
)
|
||||
|
||||
@action(detail=False, methods=['post'], url_path='disable')
|
||||
def disable(self, request):
|
||||
"""
|
||||
Disables the Strava integration for the authenticated user by deleting their StravaToken.
|
||||
"""
|
||||
strava_token = StravaToken.objects.filter(user=request.user).first()
|
||||
if not strava_token:
|
||||
return Response(
|
||||
{
|
||||
'message': 'Strava integration is not enabled for this user.',
|
||||
'error': True,
|
||||
'code': 'strava.not_enabled'
|
||||
},
|
||||
status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
|
||||
strava_token.delete()
|
||||
return Response(
|
||||
{'message': 'Strava integration disabled successfully.'},
|
||||
status=status.HTTP_204_NO_CONTENT
|
||||
)
|
||||
|
||||
def refresh_strava_token_if_needed(self, user):
|
||||
strava_token = StravaToken.objects.filter(user=user).first()
|
||||
if not strava_token:
|
||||
|
|
|
@ -747,7 +747,8 @@
|
|||
"network_error": "Network error while connecting to the Immich server. Please check your connection and try again."
|
||||
},
|
||||
"google_maps": {
|
||||
"google_maps_integration_desc": "Connect your Google Maps account to get high-quality location search results and recommendations."
|
||||
"google_maps_integration_desc": "Connect your Google Maps account to get high-quality location search results and recommendations.",
|
||||
"google_maps_integration_desc_no_staff": "This integration must first be enabled by the admin on this server."
|
||||
},
|
||||
"recomendations": {
|
||||
"recommendation": "Recommendation",
|
||||
|
@ -786,5 +787,17 @@
|
|||
"invited_on": "Invited on",
|
||||
"no_invites_desc": "Make sure your profile is public so users can invite you.",
|
||||
"by": "by"
|
||||
},
|
||||
"strava": {
|
||||
"strava_integration_desc": "Connect to Strava to easily import your activties into locations and visits",
|
||||
"not_configured": "Strava Not Configured",
|
||||
"admin_setup_required": "The server administrator must enable it globally",
|
||||
"ready_to_connect": "Ready to Connect",
|
||||
"connect_account": "Connect Account",
|
||||
"account_connected": "Account Connected",
|
||||
"disconnect": "Disconnect",
|
||||
"authorization_error": "Error redirecting to strava authorization URL",
|
||||
"disconnected": "Successfully disconnected from Strava",
|
||||
"disconnect_error": "Error disconnecting from Strava"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -80,7 +80,8 @@ export const load: PageServerLoad = async (event) => {
|
|||
}
|
||||
let integrations = await integrationsFetch.json();
|
||||
let googleMapsEnabled = integrations.google_maps as boolean;
|
||||
let stravaEnabled = integrations.strava as boolean;
|
||||
let stravaGlobalEnabled = integrations.strava.global as boolean;
|
||||
let stravaUserEnabled = integrations.strava.user as boolean;
|
||||
|
||||
let publicUrlFetch = await fetch(`${endpoint}/public-url/`);
|
||||
let publicUrl = '';
|
||||
|
@ -100,7 +101,8 @@ export const load: PageServerLoad = async (event) => {
|
|||
publicUrl,
|
||||
socialProviders,
|
||||
googleMapsEnabled,
|
||||
stravaEnabled
|
||||
stravaGlobalEnabled,
|
||||
stravaUserEnabled
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -25,7 +25,8 @@
|
|||
let public_url: string = data.props.publicUrl;
|
||||
let immichIntegration = data.props.immichIntegration;
|
||||
let googleMapsEnabled = data.props.googleMapsEnabled;
|
||||
let stravaEnabled = data.props.stravaEnabled;
|
||||
let stravaGlobalEnabled = data.props.stravaGlobalEnabled;
|
||||
let stravaUserEnabled = data.props.stravaUserEnabled;
|
||||
let activeSection: string = 'profile';
|
||||
let acknowledgeRestoreOverride: boolean = false;
|
||||
|
||||
|
@ -272,6 +273,36 @@
|
|||
addToast('error', $t('settings.generic_error'));
|
||||
}
|
||||
}
|
||||
|
||||
async function stravaAuthorizeRedirect() {
|
||||
const res = await fetch('/api/integrations/strava/authorize/', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
window.location.href = data.auth_url;
|
||||
} else {
|
||||
addToast('error', $t('strava.authorization_error'));
|
||||
}
|
||||
}
|
||||
|
||||
async function stravaDisconnect() {
|
||||
const res = await fetch('/api/integrations/strava/disable/', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
if (res.ok) {
|
||||
addToast('success', $t('strava.disconnected'));
|
||||
stravaUserEnabled = false;
|
||||
} else {
|
||||
addToast('error', $t('strava.disconnect_error'));
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if isMFAModalOpen}
|
||||
|
@ -913,7 +944,9 @@
|
|||
<div class="badge badge-error ml-auto">{$t('settings.disconnected')}</div>
|
||||
{/if}
|
||||
</div>
|
||||
{#if user.is_staff || !googleMapsEnabled}
|
||||
<div class="mt-4 p-4 bg-info/10 rounded-lg">
|
||||
{#if user.is_staff}
|
||||
<p class="text-sm">
|
||||
📖 {$t('immich.need_help')}
|
||||
<a
|
||||
|
@ -922,34 +955,76 @@
|
|||
target="_blank">{$t('navbar.documentation')}</a
|
||||
>
|
||||
</p>
|
||||
{:else if !googleMapsEnabled}
|
||||
<p class="text-sm">
|
||||
ℹ️ {$t('google_maps.google_maps_integration_desc_no_staff')}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Strava Integration Section -->
|
||||
<div class="p-6 bg-base-200 rounded-xl">
|
||||
<div class="flex items-center gap-4 mb-4">
|
||||
<img src={StravaLogo} alt="Strava" class="w-8 h-8 rounded-md" />
|
||||
<div>
|
||||
<h3 class="text-xl font-bold">Strava</h3>
|
||||
<p class="text-sm text-base-content/70">
|
||||
{$t('google_maps.google_maps_integration_desc')}
|
||||
{$t('strava.strava_integration_desc')}
|
||||
</p>
|
||||
</div>
|
||||
{#if stravaEnabled}
|
||||
{#if stravaGlobalEnabled && stravaUserEnabled}
|
||||
<div class="badge badge-success ml-auto">{$t('settings.connected')}</div>
|
||||
{:else}
|
||||
<div class="badge badge-error ml-auto">{$t('settings.disconnected')}</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Content based on integration status -->
|
||||
{#if !stravaGlobalEnabled}
|
||||
<!-- Strava not enabled globally -->
|
||||
<div class="text-center">
|
||||
<p class="text-base-content/70 mb-4">
|
||||
{$t('strava.not_enabled') ||
|
||||
'Strava integration is not enabled on this instance.'}
|
||||
</p>
|
||||
</div>
|
||||
{:else if !stravaUserEnabled && stravaGlobalEnabled}
|
||||
<!-- Globally enabled but user not connected -->
|
||||
<div class="text-center">
|
||||
<button class="btn btn-primary" on:click={stravaAuthorizeRedirect}>
|
||||
🔗 {$t('strava.connect_account')}
|
||||
</button>
|
||||
</div>
|
||||
{:else if stravaGlobalEnabled && stravaUserEnabled}
|
||||
<!-- User connected - show management options -->
|
||||
<div class="text-center">
|
||||
<button class="btn btn-error" on:click={stravaDisconnect}>
|
||||
❌ {$t('strava.disconnect')}
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Help documentation link -->
|
||||
{#if user.is_staff || !stravaGlobalEnabled}
|
||||
<div class="mt-4 p-4 bg-info/10 rounded-lg">
|
||||
{#if user.is_staff}
|
||||
<p class="text-sm">
|
||||
📖 {$t('immich.need_help')}
|
||||
<a
|
||||
class="link link-primary"
|
||||
href="https://adventurelog.app/docs/configuration/google_maps_integration.html"
|
||||
href="https://adventurelog.app/docs/configuration/strava_integration.html"
|
||||
target="_blank">{$t('navbar.documentation')}</a
|
||||
>
|
||||
</p>
|
||||
{:else if !stravaGlobalEnabled}
|
||||
<p class="text-sm">
|
||||
ℹ️ {$t('google_maps.google_maps_integration_desc_no_staff')}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue