diff --git a/backend/server/adventures/admin.py b/backend/server/adventures/admin.py
index af3fb9d..6d77b07 100644
--- a/backend/server/adventures/admin.py
+++ b/backend/server/adventures/admin.py
@@ -6,8 +6,8 @@ from worldtravel.models import Country, Region, VisitedRegion
class AdventureAdmin(admin.ModelAdmin):
- list_display = ('name', 'type', 'user_id', 'date', 'image_display')
- list_filter = ('type', 'user_id')
+ list_display = ('name', 'type', 'user_id', 'date', 'is_public', 'image_display')
+ list_filter = ('type', 'user_id', 'is_public')
def image_display(self, obj):
if obj.image:
diff --git a/backend/server/adventures/permissions.py b/backend/server/adventures/permissions.py
index f4dfbea..b4241c2 100644
--- a/backend/server/adventures/permissions.py
+++ b/backend/server/adventures/permissions.py
@@ -12,4 +12,19 @@ class IsOwnerOrReadOnly(permissions.BasePermission):
return True
# Write permissions are only allowed to the owner of the object.
+ return obj.user_id == request.user
+
+
+class IsPublicReadOnly(permissions.BasePermission):
+ """
+ Custom permission to only allow read-only access to public objects,
+ and write access to the owner of the object.
+ """
+
+ def has_object_permission(self, request, view, obj):
+ # Read permissions are allowed if the object is public
+ if request.method in permissions.SAFE_METHODS:
+ return obj.is_public or obj.user_id == request.user
+
+ # Write permissions are only allowed to the owner of the object
return obj.user_id == request.user
\ No newline at end of file
diff --git a/backend/server/adventures/views.py b/backend/server/adventures/views.py
index 2ccc5a6..b03d687 100644
--- a/backend/server/adventures/views.py
+++ b/backend/server/adventures/views.py
@@ -5,11 +5,11 @@ from .models import Adventure, Trip
from .serializers import AdventureSerializer, TripSerializer
from rest_framework.permissions import IsAuthenticated
from django.db.models import Q, Prefetch
-from .permissions import IsOwnerOrReadOnly
+from .permissions import IsOwnerOrReadOnly, IsPublicReadOnly
class AdventureViewSet(viewsets.ModelViewSet):
serializer_class = AdventureSerializer
- permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]
+ permission_classes = [IsOwnerOrReadOnly, IsPublicReadOnly]
def get_queryset(self):
return Adventure.objects.filter(
@@ -42,7 +42,7 @@ class AdventureViewSet(viewsets.ModelViewSet):
class TripViewSet(viewsets.ModelViewSet):
serializer_class = TripSerializer
- permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]
+ permission_classes = [IsOwnerOrReadOnly, IsPublicReadOnly]
def get_queryset(self):
return Trip.objects.filter(
diff --git a/frontend/src/lib/assets/undraw_lost.svg b/frontend/src/lib/assets/undraw_lost.svg
new file mode 100644
index 0000000..2d05eb1
--- /dev/null
+++ b/frontend/src/lib/assets/undraw_lost.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/frontend/src/lib/components/EditAdventure.svelte b/frontend/src/lib/components/EditAdventure.svelte
index a7a1d56..7e1d3c6 100644
--- a/frontend/src/lib/components/EditAdventure.svelte
+++ b/frontend/src/lib/components/EditAdventure.svelte
@@ -21,6 +21,7 @@
import Star from '~icons/mdi/star';
import Attachment from '~icons/mdi/attachment';
import PointSelectionModal from './PointSelectionModal.svelte';
+ import Earth from '~icons/mdi/earth';
onMount(async () => {
modal = document.getElementById('my_modal_1') as HTMLDialogElement;
@@ -245,6 +246,39 @@
Location
+
+
+
+
+
+ {#if adventureToEdit.is_public}
+
+
Share this Adventure!
+
+
+ {window.location.origin}/adventures/{adventureToEdit.id}
+
+
+
+
+ {/if}
diff --git a/frontend/src/routes/adventures/+page.server.ts b/frontend/src/routes/adventures/+page.server.ts
index 027b323..ddd3935 100644
--- a/frontend/src/routes/adventures/+page.server.ts
+++ b/frontend/src/routes/adventures/+page.server.ts
@@ -175,6 +175,13 @@ export const actions: Actions = {
let link = formData.get('link') as string | null;
let latitude = formData.get('latitude') as string | null;
let longitude = formData.get('longitude') as string | null;
+ let is_public = formData.get('is_public') as string | null | boolean;
+
+ if (is_public) {
+ is_public = true;
+ } else {
+ is_public = false;
+ }
// check if latitude and longitude are valid
if (latitude && longitude) {
@@ -221,6 +228,7 @@ export const actions: Actions = {
formDataToSend.append('description', description || '');
formDataToSend.append('latitude', latitude || '');
formDataToSend.append('longitude', longitude || '');
+ formDataToSend.append('is_public', is_public.toString());
if (activity_types) {
// Filter out empty and duplicate activity types, then trim each activity type
const cleanedActivityTypes = Array.from(
diff --git a/frontend/src/routes/adventures/[id]/+page.server.ts b/frontend/src/routes/adventures/[id]/+page.server.ts
index d187d1a..1f3eb48 100644
--- a/frontend/src/routes/adventures/[id]/+page.server.ts
+++ b/frontend/src/routes/adventures/[id]/+page.server.ts
@@ -5,33 +5,27 @@ import type { Adventure } from '$lib/types';
const endpoint = PUBLIC_SERVER_URL || 'http://localhost:8000';
export const load = (async (event) => {
- if (!event.locals.user) {
- return redirect(302, '/login');
- } else {
- const id = event.params as { id: string };
- let request = await fetch(`${endpoint}/api/adventures/${id.id}/`, {
- headers: {
- Cookie: `${event.cookies.get('auth')}`
- }
- });
- if (!request.ok) {
- console.error('Failed to fetch adventure ' + id.id);
- return {
- props: {
- adventure: null
- }
- };
- } else {
- let adventure = (await request.json()) as Adventure;
- if (!adventure.is_public && adventure.user_id !== event.locals.user.pk) {
- return redirect(302, '/');
- }
- return {
- props: {
- adventure
- }
- };
+ const id = event.params as { id: string };
+ let request = await fetch(`${endpoint}/api/adventures/${id.id}/`, {
+ headers: {
+ Cookie: `${event.cookies.get('auth')}`
}
+ });
+ if (!request.ok) {
+ console.error('Failed to fetch adventure ' + id.id);
+ return {
+ props: {
+ adventure: null
+ }
+ };
+ } else {
+ let adventure = (await request.json()) as Adventure;
+
+ return {
+ props: {
+ adventure
+ }
+ };
}
}) satisfies PageServerLoad;
diff --git a/frontend/src/routes/adventures/[id]/+page.svelte b/frontend/src/routes/adventures/[id]/+page.svelte
index a15db0c..ad938e9 100644
--- a/frontend/src/routes/adventures/[id]/+page.svelte
+++ b/frontend/src/routes/adventures/[id]/+page.svelte
@@ -18,25 +18,51 @@
import { onMount } from 'svelte';
import type { PageData } from './$types';
import { goto } from '$app/navigation';
+ import Lost from '$lib/assets/undraw_lost.svg';
export let data: PageData;
let adventure: Adventure;
+ let notFound: boolean = false;
+
onMount(() => {
if (data.props.adventure) {
adventure = data.props.adventure;
} else {
- goto('/404');
+ notFound = true;
}
});
-{#if !adventure}
+{#if notFound}
+
+
+
+

+
+
+ Adventure not Found
+
+
+ The adventure you were looking for could not be found. Please try a different adventure or
+ check back later.
+
+
+
+
+
+
+{/if}
+
+{#if !adventure && !notFound}
-{:else}
+{/if}
+{#if adventure}
{#if adventure.name}
{/if}