diff --git a/backend/server/adventures/models.py b/backend/server/adventures/models.py index 49552b0..57a62c6 100644 --- a/backend/server/adventures/models.py +++ b/backend/server/adventures/models.py @@ -44,8 +44,8 @@ class Adventure(models.Model): raise ValidationError('Adventures must be associated with trips owned by the same user. Trip owner: ' + self.trip.user_id.username + ' Adventure owner: ' + self.user_id.username) if self.type != self.trip.type: raise ValidationError('Adventure type must match trip type. Trip type: ' + self.trip.type + ' Adventure type: ' + self.type) - if self.type == 'featured' and not self.is_public: - raise ValidationError('Featured adventures must be public. Adventure: ' + self.name) + if self.type == 'featured' and not self.is_public: + raise ValidationError('Featured adventures must be public. Adventure: ' + self.name) def __str__(self): return self.name diff --git a/backend/server/adventures/urls.py b/backend/server/adventures/urls.py index 6c4b962..ed8f8bf 100644 --- a/backend/server/adventures/urls.py +++ b/backend/server/adventures/urls.py @@ -1,10 +1,11 @@ from django.urls import include, path from rest_framework.routers import DefaultRouter -from .views import AdventureViewSet, TripViewSet +from .views import AdventureViewSet, TripViewSet, StatsViewSet router = DefaultRouter() router.register(r'adventures', AdventureViewSet, basename='adventures') router.register(r'trips', TripViewSet, basename='trips') +router.register(r'stats', StatsViewSet, basename='stats') urlpatterns = [ # Include the router under the 'api/' prefix diff --git a/backend/server/adventures/views.py b/backend/server/adventures/views.py index b03d687..0d0c24c 100644 --- a/backend/server/adventures/views.py +++ b/backend/server/adventures/views.py @@ -2,6 +2,7 @@ from rest_framework.decorators import action from rest_framework import viewsets from rest_framework.response import Response from .models import Adventure, Trip +from worldtravel.models import VisitedRegion from .serializers import AdventureSerializer, TripSerializer from rest_framework.permissions import IsAuthenticated from django.db.models import Q, Prefetch @@ -72,4 +73,30 @@ class TripViewSet(viewsets.ModelViewSet): def featured(self, request): trips = self.get_queryset().filter(type='featured', is_public=True) serializer = self.get_serializer(trips, many=True) - return Response(serializer.data) \ No newline at end of file + return Response(serializer.data) + +class StatsViewSet(viewsets.ViewSet): + permission_classes = [IsAuthenticated] + + @action(detail=False, methods=['get']) + def counts(self, request): + visited_count = Adventure.objects.filter( + type='visited', user_id=request.user.id).count() + planned_count = Adventure.objects.filter( + type='planned', user_id=request.user.id).count() + featured_count = Adventure.objects.filter( + type='featured', is_public=True).count() + trips_count = Trip.objects.filter( + user_id=request.user.id).count() + region_count = VisitedRegion.objects.filter( + user_id=request.user.id).count() + country_count = VisitedRegion.objects.filter( + user_id=request.user.id).values('region__country').distinct().count() + return Response({ + 'visited_count': visited_count, + 'planned_count': planned_count, + 'featured_count': featured_count, + 'trips_count': trips_count, + 'region_count': region_count, + 'country_count': country_count, + }) \ No newline at end of file diff --git a/frontend/src/lib/components/RegionCard.svelte b/frontend/src/lib/components/RegionCard.svelte index 892c575..d044e94 100644 --- a/frontend/src/lib/components/RegionCard.svelte +++ b/frontend/src/lib/components/RegionCard.svelte @@ -10,8 +10,6 @@ export let visit_id: number | undefined | null; - console.log(visit_id); - async function markVisited() { let res = await fetch(`/worldtravel?/markVisited`, { method: 'POST', @@ -47,6 +45,7 @@ if (res.ok) { visited = false; addToast('info', `Visit to ${region.name} removed`); + dispatch('remove', null); } else { console.error('Failed to remove visit'); } diff --git a/frontend/src/lib/types.ts b/frontend/src/lib/types.ts index cba9c56..a4ad89a 100644 --- a/frontend/src/lib/types.ts +++ b/frontend/src/lib/types.ts @@ -37,7 +37,7 @@ export type Country = { export type Region = { id: number; name: string; - country_id: number; + country: number; }; export type VisitedRegion = { diff --git a/frontend/src/routes/worldtravel/+page.server.ts b/frontend/src/routes/worldtravel/+page.server.ts index 2b9eb49..4fea381 100644 --- a/frontend/src/routes/worldtravel/+page.server.ts +++ b/frontend/src/routes/worldtravel/+page.server.ts @@ -67,8 +67,6 @@ export const actions: Actions = { removeVisited: async (event) => { const body = await event.request.json(); - console.log(body); - if (!body || !body.visitId) { return { status: 400 diff --git a/frontend/src/routes/worldtravel/[id]/+page.server.ts b/frontend/src/routes/worldtravel/[id]/+page.server.ts index 182a6ae..38ed4c8 100644 --- a/frontend/src/routes/worldtravel/[id]/+page.server.ts +++ b/frontend/src/routes/worldtravel/[id]/+page.server.ts @@ -1,5 +1,5 @@ const PUBLIC_SERVER_URL = process.env['PUBLIC_SERVER_URL']; -import type { Region, VisitedRegion } from '$lib/types'; +import type { Country, Region, VisitedRegion } from '$lib/types'; import type { PageServerLoad } from './$types'; const endpoint = PUBLIC_SERVER_URL || 'http://localhost:8000'; @@ -9,6 +9,7 @@ export const load = (async (event) => { let regions: Region[] = []; let visitedRegions: VisitedRegion[] = []; + let country: Country; let res = await fetch(`${endpoint}/api/${id}/regions/`, { method: 'GET', @@ -23,6 +24,11 @@ export const load = (async (event) => { regions = (await res.json()) as Region[]; } + if (regions.length === 0) { + console.error('No regions found'); + return { status: 404 }; + } + res = await fetch(`${endpoint}/api/${id}/visits/`, { method: 'GET', headers: { @@ -36,10 +42,24 @@ export const load = (async (event) => { visitedRegions = (await res.json()) as VisitedRegion[]; } + res = await fetch(`${endpoint}/api/countries/${regions[0].country}/`, { + method: 'GET', + headers: { + Cookie: `${event.cookies.get('auth')}` + } + }); + if (!res.ok) { + console.error('Failed to fetch country'); + return { status: 500 }; + } else { + country = (await res.json()) as Country; + } + return { props: { regions, - visitedRegions + visitedRegions, + country } }; }) satisfies PageServerLoad; diff --git a/frontend/src/routes/worldtravel/[id]/+page.svelte b/frontend/src/routes/worldtravel/[id]/+page.svelte index 44030c3..aaf243f 100644 --- a/frontend/src/routes/worldtravel/[id]/+page.svelte +++ b/frontend/src/routes/worldtravel/[id]/+page.svelte @@ -5,11 +5,27 @@ export let data: PageData; let regions: Region[] = data.props?.regions || []; let visitedRegions: VisitedRegion[] = data.props?.visitedRegions || []; - + const country = data.props?.country || null; console.log(data); + + let numRegions: number = regions.length; + let numVisitedRegions: number = visitedRegions.length; -