1
0
Fork 0
mirror of https://github.com/seanmorley15/AdventureLog.git synced 2025-07-30 18:29:37 +02:00

feat: Add additional adventure type and endpoint for sunrise/sunset information

This commit is contained in:
Sean Morley 2025-03-22 12:25:53 -04:00
parent 13d3b24ec2
commit 16a7772003
5 changed files with 146 additions and 95 deletions

View file

@ -10,6 +10,7 @@ from adventures.models import Adventure, Category, Transportation, Lodging
from adventures.permissions import IsOwnerOrSharedWithFullAccess
from adventures.serializers import AdventureSerializer, TransportationSerializer, LodgingSerializer
from adventures.utils import pagination
import requests
class AdventureViewSet(viewsets.ModelViewSet):
serializer_class = AdventureSerializer
@ -170,48 +171,38 @@ class AdventureViewSet(viewsets.ModelViewSet):
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
# @action(detail=True, methods=['post'])
# def convert(self, request, pk=None):
# """
# Convert an Adventure instance into a Transportation or Lodging instance.
# Expects a JSON body with "target_type": "transportation" or "lodging".
# """
# adventure = self.get_object()
# target_type = request.data.get("target_type", "").lower()
@action(detail=True, methods=['get'], url_path='additional-info')
def additional_info(self, request, pk=None):
adventure = self.get_object()
# if target_type not in ["transportation", "lodging"]:
# return Response(
# {"error": "Invalid target type. Must be 'transportation' or 'lodging'."},
# status=400
# )
# if not adventure.collection:
# return Response(
# {"error": "Adventure must be part of a collection to be converted."},
# status=400
# )
# Permission check: owner or shared collection member
if adventure.user_id != request.user:
if not (adventure.collection and adventure.collection.shared_with.filter(id=request.user.id).exists()):
return Response({"error": "User does not have permission to access this adventure"},
status=status.HTTP_403_FORBIDDEN)
# # Define the overlapping fields that both the Adventure and target models share.
# overlapping_fields = ["name", "description", "is_public", 'collection']
serializer = self.get_serializer(adventure)
response_data = serializer.data
# # Gather the overlapping data from the adventure instance.
# conversion_data = {}
# for field in overlapping_fields:
# if hasattr(adventure, field):
# conversion_data[field] = getattr(adventure, field)
visits = response_data.get('visits', [])
sun_times = []
# # Make sure to include the user reference
# conversion_data["user_id"] = adventure.user_id
for visit in visits:
date = visit.get('start_date')
if date and adventure.longitude and adventure.latitude:
api_url = f'https://api.sunrisesunset.io/json?lat={adventure.latitude}&lng={adventure.longitude}&date={date}'
res = requests.get(api_url)
if res.status_code == 200:
data = res.json()
results = data.get('results', {})
if results.get('sunrise') and results.get('sunset'):
sun_times.append({
"date": date,
"visit_id": visit.get('id'),
"sunrise": results.get('sunrise'),
"sunset": results.get('sunset')
})
# # Convert the adventure instance within an atomic transaction.
# with transaction.atomic():
# if target_type == "transportation":
# new_instance = Transportation.objects.create(**conversion_data)
# serializer = TransportationSerializer(new_instance)
# else: # target_type == "lodging"
# new_instance = Lodging.objects.create(**conversion_data)
# serializer = LodgingSerializer(new_instance)
# # Optionally, delete the original adventure to avoid duplicates.
# adventure.delete()
# return Response(serializer.data)
response_data['sun_times'] = sun_times
return Response(response_data)

View file

@ -44,6 +44,15 @@ export type Adventure = {
user?: User | null;
};
export type AdditionalAdventure = Adventure & {
sun_times: {
date: string;
visit_id: string;
sunrise: string;
sunset: string;
}[];
};
export type Country = {
id: number;
name: string;

View file

@ -90,6 +90,8 @@
"visits": "Visits",
"create_new": "Create New...",
"adventure": "Adventure",
"additional_info": "Additional Information",
"sunrise_sunset": "Sunrise & Sunset",
"count_txt": "results matching your search",
"sort": "Sort",
"order_by": "Order By",

View file

@ -1,11 +1,11 @@
import type { PageServerLoad } from './$types';
const PUBLIC_SERVER_URL = process.env['PUBLIC_SERVER_URL'];
import type { Adventure, Collection } from '$lib/types';
import type { AdditionalAdventure, Adventure, Collection } from '$lib/types';
const endpoint = PUBLIC_SERVER_URL || 'http://localhost:8000';
export const load = (async (event) => {
const id = event.params as { id: string };
let request = await fetch(`${endpoint}/api/adventures/${id.id}/`, {
let request = await fetch(`${endpoint}/api/adventures/${id.id}/additional-info/`, {
headers: {
Cookie: `sessionid=${event.cookies.get('sessionid')}`
},
@ -19,7 +19,7 @@ export const load = (async (event) => {
}
};
} else {
let adventure = (await request.json()) as Adventure;
let adventure = (await request.json()) as AdditionalAdventure;
let collection: Collection | null = null;
if (adventure.collection) {

View file

@ -1,5 +1,5 @@
<script lang="ts">
import type { Adventure } from '$lib/types';
import type { AdditionalAdventure, Adventure } from '$lib/types';
import { onMount } from 'svelte';
import type { PageData } from './$types';
import { goto } from '$app/navigation';
@ -12,6 +12,7 @@
import toGeoJSON from '@mapbox/togeojson';
import LightbulbOn from '~icons/mdi/lightbulb-on';
import WeatherSunset from '~icons/mdi/weather-sunset';
let geojson: any;
@ -75,7 +76,7 @@
export let data: PageData;
console.log(data);
let adventure: Adventure;
let adventure: AdditionalAdventure;
let currentSlide = 0;
@ -112,7 +113,7 @@
await getGpxFiles();
});
async function saveEdit(event: CustomEvent<Adventure>) {
async function saveEdit(event: CustomEvent<AdditionalAdventure>) {
adventure = event.detail;
isEditModalOpen = false;
geojson = null;
@ -522,6 +523,53 @@
</MapLibre>
{/if}
</div>
<!-- Additional Info Display Section -->
<div>
{#if adventure.sun_times && adventure.sun_times.length > 0}
<h2 class="text-2xl font-bold mt-4 mb-4">{$t('adventures.additional_info')}</h2>
{#if adventure.sun_times && adventure.sun_times.length > 0}
<div class="collapse collapse-plus bg-base-200 mb-2 overflow-visible">
<input type="checkbox" />
<div class="collapse-title text-xl font-medium">
<span>
{$t('adventures.sunrise_sunset')}
<WeatherSunset class="w-6 h-6 inline-block ml-2 -mt-1" />
</span>
</div>
<div class="collapse-content">
<div class="grid gap-4 mt-4">
<!-- Sunrise and Sunset times -->
{#each adventure.sun_times as sun_time}
<div class="grid md:grid-cols-3 gap-4">
<div>
<p class="text-sm text-muted-foreground">Date</p>
<p class="text-base font-medium">
{new Date(sun_time.date).toLocaleDateString()}
</p>
</div>
<div>
<p class="text-sm text-muted-foreground">Sunrise</p>
<p class="text-base font-medium">
{sun_time.sunrise}
</p>
</div>
<div>
<p class="text-sm text-muted-foreground">Sunset</p>
<p class="text-base font-medium">
{sun_time.sunset}
</p>
</div>
</div>
{/each}
</div>
</div>
</div>
{/if}
{/if}
{#if adventure.attachments && adventure.attachments.length > 0}
<div>
<!-- attachments -->
@ -579,6 +627,7 @@
</div>
</div>
</div>
</div>
</main>
</div>
{/if}