diff --git a/backend/server/adventures/views.py b/backend/server/adventures/views.py
index 1ce81f5..17925b3 100644
--- a/backend/server/adventures/views.py
+++ b/backend/server/adventures/views.py
@@ -20,7 +20,7 @@ from django.contrib.auth import get_user_model
from icalendar import Calendar, Event, vText, vCalAddress
from django.http import HttpResponse
from datetime import datetime
-from django.db.models import Min
+from django.db.models import Max
User = get_user_model()
@@ -54,9 +54,9 @@ class AdventureViewSet(viewsets.ModelViewSet):
if order_by == 'date':
# order by the earliest visit object associated with the adventure
- queryset = queryset.annotate(earliest_visit=Min('visits__start_date'))
- queryset = queryset.filter(earliest_visit__isnull=False)
- ordering = 'earliest_visit'
+ queryset = queryset.annotate(latest_visit=Max('visits__start_date'))
+ queryset = queryset.filter(latest_visit__isnull=False)
+ ordering = 'latest_visit'
# Apply case-insensitive sorting for the 'name' field
elif order_by == 'name':
queryset = queryset.annotate(lower_name=Lower('name'))
@@ -1163,6 +1163,7 @@ class ReverseGeocodeViewSet(viewsets.ViewSet):
display_name = None
country_code = None
city = None
+ visited_city = None
# town = None
# city = None
@@ -1221,6 +1222,8 @@ class ReverseGeocodeViewSet(viewsets.ViewSet):
# searches through all of the users adventures, if the serialized data is_visited, is true, runs reverse geocode on the adventures and if a region is found, marks it as visited. Use the extractIsoCode function to get the region
new_region_count = 0
new_regions = {}
+ new_city_count = 0
+ new_cities = {}
adventures = Adventure.objects.filter(user_id=self.request.user)
serializer = AdventureSerializer(adventures, many=True)
for adventure, serialized_adventure in zip(adventures, serializer.data):
@@ -1234,17 +1237,25 @@ class ReverseGeocodeViewSet(viewsets.ViewSet):
data = response.json()
except requests.exceptions.JSONDecodeError:
return Response({"error": "Invalid response from geocoding service"}, status=400)
- region = self.extractIsoCode(data)
- if 'error' not in region:
- region = Region.objects.filter(id=region['id']).first()
+ extracted_region = self.extractIsoCode(data)
+ if 'error' not in extracted_region:
+ region = Region.objects.filter(id=extracted_region['region_id']).first()
visited_region = VisitedRegion.objects.filter(region=region, user_id=self.request.user).first()
if not visited_region:
visited_region = VisitedRegion(region=region, user_id=self.request.user)
visited_region.save()
new_region_count += 1
new_regions[region.id] = region.name
- return Response({"new_regions": new_region_count, "regions": new_regions})
+ if extracted_region['city_id'] is not None:
+ city = City.objects.filter(id=extracted_region['city_id']).first()
+ visited_city = VisitedCity.objects.filter(city=city, user_id=self.request.user).first()
+ if not visited_city:
+ visited_city = VisitedCity(city=city, user_id=self.request.user)
+ visited_city.save()
+ new_city_count += 1
+ new_cities[city.id] = city.name
+ return Response({"new_regions": new_region_count, "regions": new_regions, "new_cities": new_city_count, "cities": new_cities})
from django.http import HttpResponse
from rest_framework import viewsets
diff --git a/backend/server/requirements.txt b/backend/server/requirements.txt
index 0e2ccbf..4400512 100644
--- a/backend/server/requirements.txt
+++ b/backend/server/requirements.txt
@@ -1,4 +1,5 @@
Django==5.0.11
+Django==5.0.10
djangorestframework>=3.15.2
django-allauth==0.63.3
drf-yasg==1.21.4
diff --git a/backend/server/users/forms.py b/backend/server/users/forms.py
deleted file mode 100644
index 266bfd0..0000000
--- a/backend/server/users/forms.py
+++ /dev/null
@@ -1,17 +0,0 @@
-from django import forms
-
-class CustomSignupForm(forms.Form):
- first_name = forms.CharField(max_length=30, required=True)
- last_name = forms.CharField(max_length=30, required=True)
-
- def signup(self, request, user):
- # Delay the import to avoid circular import
- from allauth.account.forms import SignupForm
-
- # No need to call super() from CustomSignupForm; use the SignupForm directly if needed
- user.first_name = self.cleaned_data['first_name']
- user.last_name = self.cleaned_data['last_name']
-
- # Save the user instance
- user.save()
- return user
\ No newline at end of file
diff --git a/backend/server/users/tests.py b/backend/server/users/tests.py
index 7ce503c..08b71aa 100644
--- a/backend/server/users/tests.py
+++ b/backend/server/users/tests.py
@@ -1,3 +1,84 @@
-from django.test import TestCase
+from rest_framework.test import APITestCase
+from .models import CustomUser
+from uuid import UUID
-# Create your tests here.
+from allauth.account.models import EmailAddress
+
+class UserAPITestCase(APITestCase):
+
+ def setUp(self):
+ # Signup a new user
+ response = self.client.post('/_allauth/browser/v1/auth/signup', {
+ 'username': 'testuser',
+ 'email': 'testuser@example.com',
+ 'password': 'testpassword',
+ 'first_name': 'Test',
+ 'last_name': 'User',
+ }, format='json')
+ self.assertEqual(response.status_code, 200)
+
+ def test_001_user(self):
+ # Fetch user metadata
+ response = self.client.get('/auth/user-metadata/', format='json')
+ self.assertEqual(response.status_code, 200)
+ data = response.json()
+ print(data)
+ self.assertEqual(data['username'], 'testuser')
+ self.assertEqual(data['email'], 'testuser@example.com')
+ self.assertEqual(data['first_name'], 'Test')
+ self.assertEqual(data['last_name'], 'User')
+ self.assertEqual(data['public_profile'], False)
+ self.assertEqual(data['profile_pic'], None)
+ self.assertEqual(UUID(data['uuid']), CustomUser.objects.get(username='testuser').uuid)
+ self.assertEqual(data['is_staff'], False)
+ self.assertEqual(data['has_password'], True)
+
+ def test_002_user_update(self):
+ try:
+ userModel = CustomUser.objects.get(username='testuser2')
+ except:
+ userModel = None
+
+ self.assertEqual(userModel, None)
+ # Update user metadata
+ response = self.client.patch('/auth/update-user/', {
+ 'username': 'testuser2',
+ 'first_name': 'Test2',
+ 'last_name': 'User2',
+ 'public_profile': True,
+ }, format='json')
+ self.assertEqual(response.status_code, 200)
+
+ data = response.json()
+ # Note that the email field is not updated because that is a seperate endpoint
+ userModel = CustomUser.objects.get(username='testuser2')
+ print(userModel)
+ self.assertEqual(data['username'], 'testuser2')
+ self.assertEqual(data['email'], 'testuser@example.com')
+ self.assertEqual(data['first_name'], 'Test2')
+ self.assertEqual(data['last_name'], 'User2')
+ self.assertEqual(data['public_profile'], True)
+ self.assertEqual(data['profile_pic'], None)
+ self.assertEqual(UUID(data['uuid']), CustomUser.objects.get(username='testuser2').uuid)
+ self.assertEqual(data['is_staff'], False)
+ self.assertEqual(data['has_password'], True)
+
+ def test_003_user_add_email(self):
+ # Update user email
+ response = self.client.post('/_allauth/browser/v1/account/email', {
+ 'email': 'testuser2@example.com',
+ }, format='json')
+ self.assertEqual(response.status_code, 200)
+
+ data = response.json()
+ email_data = data['data'][0]
+
+ self.assertEqual(email_data['email'], 'testuser2@example.com')
+ self.assertEqual(email_data['primary'], False)
+ self.assertEqual(email_data['verified'], False)
+
+ emails = EmailAddress.objects.filter(user=CustomUser.objects.get(username='testuser'))
+ self.assertEqual(emails.count(), 2)
+ # assert email are testuser@example and testuser2@example.com
+ self.assertEqual(emails[1].email, 'testuser@example.com')
+ self.assertEqual(emails[0].email, 'testuser2@example.com')
diff --git a/documentation/docs/install/unraid.md b/documentation/docs/install/unraid.md
index 313ec03..cb84455 100644
--- a/documentation/docs/install/unraid.md
+++ b/documentation/docs/install/unraid.md
@@ -13,19 +13,17 @@ It is recommended to install applications in this order.
- To find the Database Application, search for `PostGIS` on the Unraid App Store and fill out the fields as follows:
- Ensure that the POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB are set in the PostGIS container if not add them custom variables
-
+
## Backend
- Cache Configuration: This option is useful only if your appdata share is stored on a cache drive, which is used to speed up read/write operations for your containerized applications.
- Note: if your running the server in a docker network that is other than "host" (for example "bridge") than you need to add the IP of the host machine in the CSRF Trusted Origins variable.
-
+
## Frontend
- By default, the frontend connects to the backend using `http://server:8000`. This will work if both the frontend and backend are on the same network. Otherwise, you’ll need to configure it to use the exposed port (default: 8016).
-
-
-
+
diff --git a/documentation/public/unraid-config-1.png b/documentation/public/unraid-config-1.png
new file mode 100644
index 0000000..36df25c
Binary files /dev/null and b/documentation/public/unraid-config-1.png differ
diff --git a/documentation/static/img/unraid-config-2.png b/documentation/public/unraid-config-2.png
similarity index 100%
rename from documentation/static/img/unraid-config-2.png
rename to documentation/public/unraid-config-2.png
diff --git a/documentation/public/unraid-config-3.png b/documentation/public/unraid-config-3.png
new file mode 100644
index 0000000..6a0e49b
Binary files /dev/null and b/documentation/public/unraid-config-3.png differ
diff --git a/documentation/static/img/unraid-config-1.png b/documentation/static/img/unraid-config-1.png
deleted file mode 100644
index bf4f50b..0000000
Binary files a/documentation/static/img/unraid-config-1.png and /dev/null differ
diff --git a/documentation/static/img/unraid-config-3.png b/documentation/static/img/unraid-config-3.png
deleted file mode 100644
index dd483a9..0000000
Binary files a/documentation/static/img/unraid-config-3.png and /dev/null differ
diff --git a/frontend/src/routes/login/+page.svelte b/frontend/src/routes/login/+page.svelte
index d6ba6b7..6fcc628 100644
--- a/frontend/src/routes/login/+page.svelte
+++ b/frontend/src/routes/login/+page.svelte
@@ -57,11 +57,14 @@
class="block input input-bordered w-full max-w-xs"
/>
{#if $page.form?.mfa_required}
-
+
{/if}