From e533dda328ac6af6b5ed45c731c442e0f70f8c24 Mon Sep 17 00:00:00 2001 From: Sean Morley Date: Mon, 15 Jul 2024 12:09:20 -0400 Subject: [PATCH] collections v2 --- backend/server/adventures/views.py | 25 ++- .../src/lib/components/AdventureCard.svelte | 8 + .../src/lib/components/AdventureLink.svelte | 59 +++++++ .../src/lib/components/CollectionCard.svelte | 35 ++-- .../src/routes/adventures/+page.server.ts | 50 +++++- frontend/src/routes/adventures/+page.svelte | 10 ++ .../routes/adventures/[id]/+page.server.ts | 70 +++++++- frontend/src/routes/collections/+page.svelte | 6 +- .../routes/collections/[id]/+page.server.ts | 9 +- .../src/routes/collections/[id]/+page.svelte | 160 +++++++++--------- frontend/src/routes/profile/+page.svelte | 2 +- 11 files changed, 332 insertions(+), 102 deletions(-) create mode 100644 frontend/src/lib/components/AdventureLink.svelte diff --git a/backend/server/adventures/views.py b/backend/server/adventures/views.py index 3800098..629d603 100644 --- a/backend/server/adventures/views.py +++ b/backend/server/adventures/views.py @@ -30,6 +30,7 @@ class AdventureViewSet(viewsets.ModelViewSet): def apply_sorting(self, queryset): order_by = self.request.query_params.get('order_by', 'name') order_direction = self.request.query_params.get('order_direction', 'asc') + include_collections = self.request.query_params.get('include_collections', 'false') valid_order_by = ['name', 'type', 'date', 'rating'] if order_by not in valid_order_by: @@ -50,6 +51,9 @@ class AdventureViewSet(viewsets.ModelViewSet): print(f"Ordering by: {ordering}") # For debugging + if include_collections == 'false': + queryset = queryset.filter(collection = None) + return queryset.order_by(ordering) def get_queryset(self): @@ -76,7 +80,7 @@ class AdventureViewSet(viewsets.ModelViewSet): for adventure_type in types: if adventure_type in ['visited', 'planned']: queryset |= Adventure.objects.filter( - type=adventure_type, user_id=request.user.id, collection=None) + type=adventure_type, user_id=request.user.id) queryset = self.apply_sorting(queryset) adventures = self.paginate_and_respond(queryset, request) @@ -86,8 +90,25 @@ class AdventureViewSet(viewsets.ModelViewSet): def all(self, request): if not request.user.is_authenticated: return Response({"error": "User is not authenticated"}, status=400) - queryset = Adventure.objects.filter(user_id=request.user.id) + # include_collections = request.query_params.get('include_collections', 'false') + # if include_collections not in ['true', 'false']: + # include_collections = 'false' + + # if include_collections == 'true': + # queryset = Adventure.objects.filter( + # Q(is_public=True) | Q(user_id=request.user.id) + # ) + # else: + # queryset = Adventure.objects.filter( + # Q(is_public=True) | Q(user_id=request.user.id), collection=None + # ) + queryset = Adventure.objects.filter( + Q(is_public=True) | Q(user_id=request.user.id) + ) + + queryset = self.apply_sorting(queryset) serializer = self.get_serializer(queryset, many=True) + return Response(serializer.data) def paginate_and_respond(self, queryset, request): diff --git a/frontend/src/lib/components/AdventureCard.svelte b/frontend/src/lib/components/AdventureCard.svelte index d2685d8..6510832 100644 --- a/frontend/src/lib/components/AdventureCard.svelte +++ b/frontend/src/lib/components/AdventureCard.svelte @@ -10,6 +10,7 @@ import Calendar from '~icons/mdi/calendar'; import MapMarker from '~icons/mdi/map-marker'; import { addToast } from '$lib/toasts'; + import Link from '~icons/mdi/link-variant'; export let type: string; @@ -34,6 +35,10 @@ function editAdventure() { dispatch('edit', adventure); } + + function link() { + dispatch('link', adventure); + }
{/if} + {#if type == 'link'} + + {/if}
diff --git a/frontend/src/lib/components/AdventureLink.svelte b/frontend/src/lib/components/AdventureLink.svelte new file mode 100644 index 0000000..fd0d312 --- /dev/null +++ b/frontend/src/lib/components/AdventureLink.svelte @@ -0,0 +1,59 @@ + + + + + + + diff --git a/frontend/src/lib/components/CollectionCard.svelte b/frontend/src/lib/components/CollectionCard.svelte index 1cd8b8a..16fc6d4 100644 --- a/frontend/src/lib/components/CollectionCard.svelte +++ b/frontend/src/lib/components/CollectionCard.svelte @@ -8,25 +8,28 @@ import { goto } from '$app/navigation'; import type { Collection } from '$lib/types'; + import { addToast } from '$lib/toasts'; const dispatch = createEventDispatcher(); // export let type: String; export let collection: Collection; - // function remove() { - // dispatch("remove", trip.id); - // } - // function edit() {} - // function add() { - // dispatch("add", trip); - // } - - // // TODO: Implement markVisited function - // function markVisited() { - // console.log(trip.id); - // dispatch("markVisited", trip); - // } + async function deleteCollection() { + let res = await fetch(`/collections/${collection.id}?/delete`, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + } + }); + if (res.ok) { + console.log('Collection deleted'); + addToast('info', 'Adventure deleted successfully!'); + dispatch('delete', collection.id); + } else { + console.log('Error deleting adventure'); + } + }
{collection.name}

{collection.adventures.length} Adventures

- - +
diff --git a/frontend/src/routes/adventures/+page.server.ts b/frontend/src/routes/adventures/+page.server.ts index aa37dac..fa30f52 100644 --- a/frontend/src/routes/adventures/+page.server.ts +++ b/frontend/src/routes/adventures/+page.server.ts @@ -362,6 +362,13 @@ export const actions: Actions = { const visited = formData.get('visited'); const planned = formData.get('planned'); + let include_collections = formData.get('include_collections') as string; + + if (include_collections) { + include_collections = 'true'; + } else { + include_collections = 'false'; + } const order_direction = formData.get('order_direction') as string; const order_by = formData.get('order_by') as string; @@ -397,7 +404,7 @@ export const actions: Actions = { console.log(filterString); let visitedFetch = await fetch( - `${serverEndpoint}/api/adventures/filtered?types=${filterString}&order_by=${order_by}&order_direction=${order_direction}`, + `${serverEndpoint}/api/adventures/filtered?types=${filterString}&order_by=${order_by}&order_direction=${order_direction}&include_collections=${include_collections}`, { headers: { Cookie: `${event.cookies.get('auth')}` @@ -502,5 +509,46 @@ export const actions: Actions = { body: { error: 'Failed to fetch data' } }; } + }, + all: async (event) => { + if (!event.locals.user) { + return { + status: 401, + body: { message: 'Unauthorized' } + }; + } + + const formData = await event.request.formData(); + + let include_collections = formData.get('include_collections') as string; + + if (include_collections !== 'true' && include_collections !== 'false') { + include_collections = 'false'; + } + + let adventures: Adventure[] = []; + + let visitedFetch = await fetch( + `${serverEndpoint}/api/adventures/all/?include_collections=${include_collections}`, + { + headers: { + Cookie: `${event.cookies.get('auth')}`, + 'Content-Type': 'application/json' + } + } + ); + if (!visitedFetch.ok) { + console.error('Failed to fetch all adventures'); + return redirect(302, '/login'); + } else { + console.log('Fetched all adventures'); + let res = await visitedFetch.json(); + console.log(res); + adventures = res as Adventure[]; + } + + return { + adventures + }; } }; diff --git a/frontend/src/routes/adventures/+page.svelte b/frontend/src/routes/adventures/+page.svelte index 3361fe3..cc819ab 100644 --- a/frontend/src/routes/adventures/+page.svelte +++ b/frontend/src/routes/adventures/+page.svelte @@ -281,6 +281,16 @@ id="rating" class="radio radio-primary" /> +
+
diff --git a/frontend/src/routes/adventures/[id]/+page.server.ts b/frontend/src/routes/adventures/[id]/+page.server.ts index 1f3eb48..109c54e 100644 --- a/frontend/src/routes/adventures/[id]/+page.server.ts +++ b/frontend/src/routes/adventures/[id]/+page.server.ts @@ -1,4 +1,3 @@ -import { redirect } from '@sveltejs/kit'; import type { PageServerLoad } from './$types'; const PUBLIC_SERVER_URL = process.env['PUBLIC_SERVER_URL']; import type { Adventure } from '$lib/types'; @@ -89,5 +88,74 @@ export const actions: Actions = { status: 204 }; } + }, + addToCollection: async (event) => { + const id = event.params as { id: string }; + const adventureId = id.id; + + const formData = await event.request.formData(); + const trip_id = formData.get('collection_id'); + + if (!trip_id) { + return { + status: 400, + error: { message: 'Missing collection id' } + }; + } + + if (!event.locals.user) { + const refresh = event.cookies.get('refresh'); + let auth = event.cookies.get('auth'); + if (!refresh) { + return { + status: 401, + body: { message: 'Unauthorized' } + }; + } + let res = await tryRefreshToken(refresh); + if (res) { + auth = res; + event.cookies.set('auth', auth, { + httpOnly: true, + sameSite: 'lax', + expires: new Date(Date.now() + 60 * 60 * 1000), // 60 minutes + path: '/' + }); + } else { + return { + status: 401, + body: { message: 'Unauthorized' } + }; + } + } + if (!adventureId) { + return { + status: 400, + error: new Error('Bad request') + }; + } + + let trip_id_number: number = parseInt(trip_id as string); + + let res = await fetch(`${serverEndpoint}/api/adventures/${event.params.id}/`, { + method: 'PATCH', + headers: { + Cookie: `${event.cookies.get('auth')}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ collection: trip_id_number }) + }); + let res2 = await res.json(); + console.log(res2); + if (!res.ok) { + return { + status: res.status, + error: new Error('Failed to delete adventure') + }; + } else { + return { + status: 204 + }; + } } }; diff --git a/frontend/src/routes/collections/+page.svelte b/frontend/src/routes/collections/+page.svelte index c12177d..9594661 100644 --- a/frontend/src/routes/collections/+page.svelte +++ b/frontend/src/routes/collections/+page.svelte @@ -66,6 +66,10 @@ }; } + function deleteCollection(event: CustomEvent) { + collections = collections.filter((collection) => collection.id !== event.detail); + } + function sort({ attribute, order }: { attribute: string; order: string }) { currentSort.attribute = attribute; currentSort.order = order; @@ -174,7 +178,7 @@ {#if currentView == 'cards'}
{#each collections as collection} - + {/each}
{/if} diff --git a/frontend/src/routes/collections/[id]/+page.server.ts b/frontend/src/routes/collections/[id]/+page.server.ts index 3a2de9d..80d7ef9 100644 --- a/frontend/src/routes/collections/[id]/+page.server.ts +++ b/frontend/src/routes/collections/[id]/+page.server.ts @@ -1,7 +1,7 @@ import { redirect } from '@sveltejs/kit'; import type { PageServerLoad } from './$types'; const PUBLIC_SERVER_URL = process.env['PUBLIC_SERVER_URL']; -import type { Adventure } from '$lib/types'; +import type { Adventure, Collection } from '$lib/types'; const endpoint = PUBLIC_SERVER_URL || 'http://localhost:8000'; export const load = (async (event) => { @@ -19,7 +19,7 @@ export const load = (async (event) => { } }; } else { - let collection = (await request.json()) as Adventure; + let collection = (await request.json()) as Collection; return { props: { @@ -71,18 +71,19 @@ export const actions: Actions = { }; } - let res = await fetch(`${serverEndpoint}/api/adventures/${event.params.id}`, { + let res = await fetch(`${serverEndpoint}/api/collections/${event.params.id}`, { method: 'DELETE', headers: { Cookie: `${event.cookies.get('auth')}`, 'Content-Type': 'application/json' } }); + console.log(res); if (!res.ok) { return { status: res.status, - error: new Error('Failed to delete adventure') + error: new Error('Failed to delete collection') }; } else { return { diff --git a/frontend/src/routes/collections/[id]/+page.svelte b/frontend/src/routes/collections/[id]/+page.svelte index ad938e9..fee2bae 100644 --- a/frontend/src/routes/collections/[id]/+page.svelte +++ b/frontend/src/routes/collections/[id]/+page.svelte @@ -1,40 +1,65 @@ - - +{#if isShowingCreateModal} + { + isShowingCreateModal = false; + }} + on:add={addAdventure} + /> +{/if} + {#if notFound}
{/if} -{#if !adventure && !notFound} +{#if !collection && !notFound}
{/if} -{#if adventure} - {#if adventure.name} -

{adventure.name}

- {/if} - {#if adventure.location} -

- {adventure.location} -

- {/if} - {#if adventure.date} -

- Visited on: {adventure.date} -

- {/if} - {#if adventure.rating !== undefined && adventure.rating !== null} -
-
- {#each Array.from({ length: 5 }, (_, i) => i + 1) as star} - - {/each} +{#if collection} +
+
+
+
+ {#if collection.name} +

{collection.name}

{/if} - {#if adventure.description} -

{adventure.description}

- {/if} - {#if adventure.link} - - {/if} - {#if adventure.activity_types && adventure.activity_types.length > 0} -
-

Activities: 

-
    - {#each adventure.activity_types as activity} -
    - {activity} -
    - {/each} -
-
- {/if} - {#if adventure.image} -
- - Adventure Image -
+

Linked Adventures

+
+ {#each adventures as adventure} + + {/each} +
+ + {#if collection.description} +

{collection.description}

{/if} {/if} diff --git a/frontend/src/routes/profile/+page.svelte b/frontend/src/routes/profile/+page.svelte index d3ff303..75b1553 100644 --- a/frontend/src/routes/profile/+page.svelte +++ b/frontend/src/routes/profile/+page.svelte @@ -75,7 +75,7 @@
-
Trips
+
Collections
{stats.trips_count}