Skip to content

Commit

Permalink
Merge pull request #157 from seanmorley15/development
Browse files Browse the repository at this point in the history
Development
  • Loading branch information
seanmorley15 authored Aug 4, 2024
2 parents 0e44b1c + 8dd44e2 commit c150eee
Show file tree
Hide file tree
Showing 18 changed files with 738 additions and 37 deletions.
3 changes: 2 additions & 1 deletion backend/server/adventures/admin.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import os
from django.contrib import admin
from django.utils.html import mark_safe
from .models import Adventure, Collection, Transportation
from .models import Adventure, Collection, Transportation, Note
from worldtravel.models import Country, Region, VisitedRegion


Expand Down Expand Up @@ -75,6 +75,7 @@ def adventure_count(self, obj):
admin.site.register(Region, RegionAdmin)
admin.site.register(VisitedRegion)
admin.site.register(Transportation)
admin.site.register(Note)

admin.site.site_header = 'AdventureLog Admin'
admin.site.site_title = 'AdventureLog Admin Site'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Generated by Django 5.0.7 on 2024-08-04 01:01

import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('adventures', '0013_alter_adventure_type_transportation'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.AlterField(
model_name='transportation',
name='type',
field=models.CharField(choices=[('car', 'Car'), ('plane', 'Plane'), ('train', 'Train'), ('bus', 'Bus'), ('boat', 'Boat'), ('bike', 'Bike'), ('walking', 'Walking'), ('other', 'Other')], max_length=100),
),
migrations.CreateModel(
name='Note',
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
('name', models.CharField(max_length=200)),
('content', models.TextField(blank=True, null=True)),
('date', models.DateTimeField(blank=True, null=True)),
('is_public', models.BooleanField(default=False)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('collection', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='adventures.collection')),
('user_id', models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]
18 changes: 18 additions & 0 deletions backend/server/adventures/migrations/0015_alter_note_date.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 5.0.7 on 2024-08-04 01:09

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('adventures', '0014_alter_transportation_type_note'),
]

operations = [
migrations.AlterField(
model_name='note',
name='date',
field=models.DateField(blank=True, null=True),
),
]
18 changes: 18 additions & 0 deletions backend/server/adventures/migrations/0016_alter_note_date.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 5.0.7 on 2024-08-04 02:00

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('adventures', '0015_alter_note_date'),
]

operations = [
migrations.AlterField(
model_name='note',
name='date',
field=models.DateTimeField(blank=True, null=True),
),
]
18 changes: 18 additions & 0 deletions backend/server/adventures/migrations/0017_alter_note_date.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 5.0.7 on 2024-08-04 02:01

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('adventures', '0016_alter_note_date'),
]

operations = [
migrations.AlterField(
model_name='note',
name='date',
field=models.DateField(blank=True, null=True),
),
]
19 changes: 19 additions & 0 deletions backend/server/adventures/migrations/0018_note_links.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Generated by Django 5.0.7 on 2024-08-04 13:19

import django.contrib.postgres.fields
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('adventures', '0017_alter_note_date'),
]

operations = [
migrations.AddField(
model_name='note',
name='links',
field=django.contrib.postgres.fields.ArrayField(base_field=models.URLField(), blank=True, null=True, size=None),
),
]
25 changes: 23 additions & 2 deletions backend/server/adventures/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,6 @@ def clean(self):
def __str__(self):
return self.name

# make a class for transportaiotn and make it linked to a collection. Make it so it can be used for different types of transportations like car, plane, train, etc.

class Transportation(models.Model):
id = models.AutoField(primary_key=True)
user_id = models.ForeignKey(
Expand Down Expand Up @@ -111,3 +109,26 @@ def clean(self):

def __str__(self):
return self.name

class Note(models.Model):
id = models.AutoField(primary_key=True)
user_id = models.ForeignKey(
User, on_delete=models.CASCADE, default=default_user_id)
name = models.CharField(max_length=200)
content = models.TextField(blank=True, null=True)
links = ArrayField(models.URLField(), blank=True, null=True)
date = models.DateField(blank=True, null=True)
is_public = models.BooleanField(default=False)
collection = models.ForeignKey('Collection', on_delete=models.CASCADE, blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)

def clean(self):
if self.collection:
if self.collection.is_public and not self.is_public:
raise ValidationError('Notes associated with a public collection must be public. Collection: ' + self.collection.name + ' Transportation: ' + self.name)
if self.user_id != self.collection.user_id:
raise ValidationError('Notes must be associated with collections owned by the same user. Collection owner: ' + self.collection.user_id.username + ' Transportation owner: ' + self.user_id.username)

def __str__(self):
return self.name
40 changes: 35 additions & 5 deletions backend/server/adventures/serializers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import os
from .models import Adventure, Collection, Transportation
from .models import Adventure, Collection, Note, Transportation
from rest_framework import serializers

class AdventureSerializer(serializers.ModelSerializer):
Expand Down Expand Up @@ -57,15 +57,45 @@ def create(self, validated_data):
validated_data['user_id'] = self.context['request'].user
return super().create(validated_data)

class NoteSerializer(serializers.ModelSerializer):

class Meta:
model = Note
fields = [
'id', 'user_id', 'name', 'content', 'date', 'links',
'is_public', 'collection', 'created_at', 'updated_at'
]
read_only_fields = ['id', 'created_at', 'updated_at']

def validate(self, data):
# Check if the collection is public and the transportation is not
collection = data.get('collection')
is_public = data.get('is_public', False)
if collection and collection.is_public and not is_public:
raise serializers.ValidationError(
'Notes associated with a public collection must be public.'
)

# Check if the user owns the collection
request = self.context.get('request')
if request and collection and collection.user_id != request.user:
raise serializers.ValidationError(
'Notes must be associated with collections owned by the same user.'
)

return data

def create(self, validated_data):
# Set the user_id to the current user
validated_data['user_id'] = self.context['request'].user
return super().create(validated_data)

class CollectionSerializer(serializers.ModelSerializer):
adventures = AdventureSerializer(many=True, read_only=True, source='adventure_set')
transportations = TransportationSerializer(many=True, read_only=True, source='transportation_set')
notes = NoteSerializer(many=True, read_only=True, source='note_set')

class Meta:
model = Collection
# fields are all plus the adventures field
fields = ['id', 'description', 'user_id', 'name', 'is_public', 'adventures', 'created_at', 'start_date', 'end_date', 'transportations']



fields = ['id', 'description', 'user_id', 'name', 'is_public', 'adventures', 'created_at', 'start_date', 'end_date', 'transportations', 'notes']
3 changes: 2 additions & 1 deletion backend/server/adventures/urls.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from django.urls import include, path
from rest_framework.routers import DefaultRouter
from .views import AdventureViewSet, CollectionViewSet, StatsViewSet, GenerateDescription, ActivityTypesView, TransportationViewSet
from .views import AdventureViewSet, CollectionViewSet, NoteViewSet, StatsViewSet, GenerateDescription, ActivityTypesView, TransportationViewSet

router = DefaultRouter()
router.register(r'adventures', AdventureViewSet, basename='adventures')
Expand All @@ -9,6 +9,7 @@
router.register(r'generate', GenerateDescription, basename='generate')
router.register(r'activity-types', ActivityTypesView, basename='activity-types')
router.register(r'transportations', TransportationViewSet, basename='transportations')
router.register(r'notes', NoteViewSet, basename='notes')


urlpatterns = [
Expand Down
48 changes: 44 additions & 4 deletions backend/server/adventures/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
from rest_framework import viewsets
from django.db.models.functions import Lower
from rest_framework.response import Response
from .models import Adventure, Collection, Transportation
from .models import Adventure, Collection, Transportation, Note
from worldtravel.models import VisitedRegion, Region, Country
from .serializers import AdventureSerializer, CollectionSerializer, TransportationSerializer
from .serializers import AdventureSerializer, CollectionSerializer, NoteSerializer, TransportationSerializer
from rest_framework.permissions import IsAuthenticated
from django.db.models import Q, Prefetch
from .permissions import IsOwnerOrReadOnly, IsPublicReadOnly
Expand Down Expand Up @@ -279,6 +279,9 @@ def update(self, request, *args, **kwargs):
# do the same for transportations
Transportation.objects.filter(collection=instance).update(is_public=new_public_status)

# do the same for notes
Note.objects.filter(collection=instance).update(is_public=new_public_status)

# Log the action (optional)
action = "public" if new_public_status else "private"
print(f"Collection {instance.id} and its adventures were set to {action}")
Expand Down Expand Up @@ -313,6 +316,10 @@ def get_queryset(self):
Prefetch('transportation_set', queryset=Transportation.objects.filter(
Q(is_public=True) | Q(user_id=self.request.user.id)
))
).prefetch_related(
Prefetch('note_set', queryset=Note.objects.filter(
Q(is_public=True) | Q(user_id=self.request.user.id)
))
)
return self.apply_sorting(adventures)

Expand Down Expand Up @@ -424,7 +431,7 @@ class TransportationViewSet(viewsets.ModelViewSet):
# return error message if user is not authenticated on the root endpoint
def list(self, request, *args, **kwargs):
# Prevent listing all adventures
return Response({"detail": "Listing all adventures is not allowed."},
return Response({"detail": "Listing all transportations is not allowed."},
status=status.HTTP_403_FORBIDDEN)

@action(detail=False, methods=['get'])
Expand All @@ -450,4 +457,37 @@ def get_queryset(self):
def perform_create(self, serializer):
serializer.save(user_id=self.request.user)


class NoteViewSet(viewsets.ModelViewSet):
queryset = Note.objects.all()
serializer_class = NoteSerializer
permission_classes = [IsAuthenticated]
filterset_fields = ['is_public', 'collection']

# return error message if user is not authenticated on the root endpoint
def list(self, request, *args, **kwargs):
# Prevent listing all adventures
return Response({"detail": "Listing all notes is not allowed."},
status=status.HTTP_403_FORBIDDEN)

@action(detail=False, methods=['get'])
def all(self, request):
if not request.user.is_authenticated:
return Response({"error": "User is not authenticated"}, status=400)
queryset = Note.objects.filter(
Q(user_id=request.user.id)
)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)


def get_queryset(self):

"""
This view should return a list of all notes
for the currently authenticated user.
"""
user = self.request.user
return Note.objects.filter(user_id=user)

def perform_create(self, serializer):
serializer.save(user_id=self.request.user)
8 changes: 7 additions & 1 deletion frontend/src/hooks.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,14 @@ import { fetchCSRFToken, tryRefreshToken } from '$lib/index.server';
export const authHook: Handle = async ({ event, resolve }) => {
try {
let authCookie = event.cookies.get('auth');
let refreshCookie = event.cookies.get('refresh');

if (!authCookie) {
if (!authCookie && !refreshCookie) {
event.locals.user = null;
return await resolve(event);
}

if (!authCookie && refreshCookie) {
event.locals.user = null;
const token = await tryRefreshToken(event.cookies.get('refresh') || '');
if (token) {
Expand Down
53 changes: 53 additions & 0 deletions frontend/src/lib/components/NoteCard.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { addToast } from '$lib/toasts';
import type { Note, User } from '$lib/types';
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
import Launch from '~icons/mdi/launch';
import TrashCan from '~icons/mdi/trash-can';
export let note: Note;
export let user: User | null = null;
function editNote() {
dispatch('edit', note);
}
async function deleteNote() {
const res = await fetch(`/api/notes/${note.id}`, {
method: 'DELETE'
});
if (res.ok) {
addToast('success', 'Note deleted successfully');
dispatch('delete', note.id);
} else {
addToast('Failed to delete note', 'error');
}
}
</script>

<div
class="card w-full max-w-xs sm:max-w-sm md:max-w-md lg:max-w-md xl:max-w-md bg-primary-content shadow-xl overflow-hidden text-base-content"
>
<div class="card-body">
<h2 class="card-title overflow-ellipsis">{note.name}</h2>
<div class="card-actions justify-end">
<!-- <button class="btn btn-neutral mb-2" on:click={() => goto(`/notes/${note.id}`)}
><Launch class="w-6 h-6" />Open Details</button
> -->
<button class="btn btn-neutral mb-2" on:click={editNote}>
<Launch class="w-6 h-6" />Open
</button>
{#if note.user_id == user?.pk}
<button
id="delete_adventure"
data-umami-event="Delete Adventure"
class="btn btn-warning"
on:click={deleteNote}><TrashCan class="w-6 h-6" />Delete</button
>
{/if}
</div>
</div>
</div>
Loading

0 comments on commit c150eee

Please sign in to comment.