From 283431085f84f351cff6ec9addb21785d6bf6851 Mon Sep 17 00:00:00 2001 From: Sean Morley Date: Wed, 21 Aug 2024 18:59:18 -0400 Subject: [PATCH 01/13] Fully remove cron jobs for new system --- backend/server/main/settings.py | 7 +------ backend/server/requirements.txt | 3 +-- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/backend/server/main/settings.py b/backend/server/main/settings.py index c19b5ea9..5515c9b8 100644 --- a/backend/server/main/settings.py +++ b/backend/server/main/settings.py @@ -58,7 +58,6 @@ 'adventures', 'worldtravel', 'users', - # 'django_apscheduler', ) @@ -228,11 +227,9 @@ 'LOGOUT_URL': 'logout', } - # For demo purposes only. Use a white list in the real world. CORS_ORIGIN_ALLOW_ALL = True - from os import getenv CSRF_TRUSTED_ORIGINS = [origin.strip() for origin in getenv('CSRF_TRUSTED_ORIGINS', 'http://localhost').split(',') if origin.strip()] @@ -261,6 +258,4 @@ 'propagate': False, }, }, -} - -SCHEDULER_AUTOSTART = True \ No newline at end of file +} \ No newline at end of file diff --git a/backend/server/requirements.txt b/backend/server/requirements.txt index 179ef7e9..2bd5846f 100644 --- a/backend/server/requirements.txt +++ b/backend/server/requirements.txt @@ -10,5 +10,4 @@ python-dotenv psycopg2-binary Pillow whitenoise -django-resized -django-apscheduler \ No newline at end of file +django-resized \ No newline at end of file From 45196f9823130e85b11b9b8d1dfa6d2e07ce878e Mon Sep 17 00:00:00 2001 From: Sean Morley Date: Fri, 23 Aug 2024 13:56:27 -0400 Subject: [PATCH 02/13] GEO Point checker! --- backend/server/main/settings.py | 3 +- backend/server/requirements.txt | 3 +- backend/server/static/data/fr.json | 2 +- backend/server/static/data/mx.json | 2 +- backend/server/static/data/us.json | 2 +- .../management/commands/worldtravel-seed.py | 60 +++++++++++++++++++ .../migrations/0004_country_geometry.py | 19 ++++++ ...remove_country_geometry_region_geometry.py | 23 +++++++ backend/server/worldtravel/models.py | 3 + backend/server/worldtravel/serializers.py | 2 +- backend/server/worldtravel/views.py | 16 +++++ 11 files changed, 129 insertions(+), 6 deletions(-) create mode 100644 backend/server/worldtravel/migrations/0004_country_geometry.py create mode 100644 backend/server/worldtravel/migrations/0005_remove_country_geometry_region_geometry.py diff --git a/backend/server/main/settings.py b/backend/server/main/settings.py index 5515c9b8..4d9e788e 100644 --- a/backend/server/main/settings.py +++ b/backend/server/main/settings.py @@ -58,6 +58,7 @@ 'adventures', 'worldtravel', 'users', + 'django.contrib.gis', ) @@ -100,7 +101,7 @@ DATABASES = { 'default': { - 'ENGINE': 'django.db.backends.postgresql', + 'ENGINE': 'django.contrib.gis.db.backends.postgis', 'NAME': getenv('PGDATABASE'), 'USER': getenv('PGUSER'), 'PASSWORD': getenv('PGPASSWORD'), diff --git a/backend/server/requirements.txt b/backend/server/requirements.txt index 2bd5846f..41a47476 100644 --- a/backend/server/requirements.txt +++ b/backend/server/requirements.txt @@ -10,4 +10,5 @@ python-dotenv psycopg2-binary Pillow whitenoise -django-resized \ No newline at end of file +django-resized +django-geojson \ No newline at end of file diff --git a/backend/server/static/data/fr.json b/backend/server/static/data/fr.json index 405893a6..8574bd43 100644 --- a/backend/server/static/data/fr.json +++ b/backend/server/static/data/fr.json @@ -4,7 +4,7 @@ { "type": "Feature", "geometry": { - "type": "Polygon", + "type": "MultiPolygon", "coordinates": [ [ [1.9221462784913, 48.457599361977], diff --git a/backend/server/static/data/mx.json b/backend/server/static/data/mx.json index b11ba3cd..4342ced8 100644 --- a/backend/server/static/data/mx.json +++ b/backend/server/static/data/mx.json @@ -10,7 +10,7 @@ "ISOCODE": "MX-CMX" }, "geometry": { - "type": "Polygon", + "type": "MultiPolygon", "coordinates": [ [ [-99.111241, 19.561498], diff --git a/backend/server/static/data/us.json b/backend/server/static/data/us.json index 9480fd07..557f51df 100644 --- a/backend/server/static/data/us.json +++ b/backend/server/static/data/us.json @@ -16,7 +16,7 @@ "AWATER": 23736382213 }, "geometry": { - "type": "Polygon", + "type": "MultiPolygon", "coordinates": [ [ [-94.0430515276176, 32.6930299766656], diff --git a/backend/server/worldtravel/management/commands/worldtravel-seed.py b/backend/server/worldtravel/management/commands/worldtravel-seed.py index f317bda7..b63d97f6 100644 --- a/backend/server/worldtravel/management/commands/worldtravel-seed.py +++ b/backend/server/worldtravel/management/commands/worldtravel-seed.py @@ -4,11 +4,66 @@ import requests from worldtravel.models import Country, Region from django.db import transaction +from django.contrib.gis.geos import GEOSGeometry, Polygon, MultiPolygon +from django.contrib.gis.geos.error import GEOSException +import json from django.conf import settings media_root = settings.MEDIA_ROOT + +def setGeometry(region_code): + # Assuming the file name is the country code (e.g., 'AU.json' for Australia) + country_code = region_code.split('-')[0] + json_file = os.path.join('static/data', f'{country_code.lower()}.json') + + if not os.path.exists(json_file): + print(f'File {country_code}.json does not exist') + return None + + try: + with open(json_file, 'r') as f: + geojson_data = json.load(f) + except json.JSONDecodeError as e: + print(f"Invalid JSON in file for {country_code}: {e}") + return None + + if 'type' not in geojson_data or geojson_data['type'] != 'FeatureCollection': + print(f"Invalid GeoJSON structure for {country_code}: missing or incorrect 'type'") + return None + + if 'features' not in geojson_data or not geojson_data['features']: + print(f"Invalid GeoJSON structure for {country_code}: missing or empty 'features'") + return None + + for feature in geojson_data['features']: + try: + properties = feature.get('properties', {}) + isocode = properties.get('ISOCODE') + + if isocode == region_code: + geometry = feature['geometry'] + geos_geom = GEOSGeometry(json.dumps(geometry)) + + if isinstance(geos_geom, Polygon): + Region.objects.filter(id=region_code).update(geometry=MultiPolygon([geos_geom])) + print(f"Updated geometry for region {region_code}") + return MultiPolygon([geos_geom]) + elif isinstance(geos_geom, MultiPolygon): + Region.objects.filter(id=region_code).update(geometry=geos_geom) + print(f"Updated geometry for region {region_code}") + return geos_geom + else: + print(f"Unexpected geometry type for region {region_code}: {type(geos_geom)}") + return None + + except (KeyError, ValueError, GEOSException) as e: + print(f"Error processing region {region_code}: {e}") + + print(f"No matching region found for {region_code}") + return None + def saveCountryFlag(country_code): flags_dir = os.path.join(media_root, 'flags') @@ -616,7 +671,9 @@ def sync_regions(self, regions): ) if created: self.stdout.write(f'Inserted {name} into worldtravel regions') + setGeometry(id) else: + setGeometry(id) self.stdout.write(f'Updated {name} in worldtravel regions') def insert_countries(self, countries): @@ -627,6 +684,7 @@ def insert_countries(self, countries): ) if created: saveCountryFlag(country_code) + self.stdout.write(f'Inserted {name} into worldtravel countries') else: saveCountryFlag(country_code) @@ -641,5 +699,7 @@ def insert_regions(self, regions): ) if created: self.stdout.write(f'Inserted {name} into worldtravel regions') + setGeometry(id) else: + setGeometry(id) self.stdout.write(f'{name} already exists in worldtravel regions') \ No newline at end of file diff --git a/backend/server/worldtravel/migrations/0004_country_geometry.py b/backend/server/worldtravel/migrations/0004_country_geometry.py new file mode 100644 index 00000000..89c8f2da --- /dev/null +++ b/backend/server/worldtravel/migrations/0004_country_geometry.py @@ -0,0 +1,19 @@ +# Generated by Django 5.0.8 on 2024-08-23 17:01 + +import django.contrib.gis.db.models.fields +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('worldtravel', '0003_alter_region_name_en'), + ] + + operations = [ + migrations.AddField( + model_name='country', + name='geometry', + field=django.contrib.gis.db.models.fields.MultiPolygonField(blank=True, null=True, srid=4326), + ), + ] diff --git a/backend/server/worldtravel/migrations/0005_remove_country_geometry_region_geometry.py b/backend/server/worldtravel/migrations/0005_remove_country_geometry_region_geometry.py new file mode 100644 index 00000000..7a32c719 --- /dev/null +++ b/backend/server/worldtravel/migrations/0005_remove_country_geometry_region_geometry.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.8 on 2024-08-23 17:47 + +import django.contrib.gis.db.models.fields +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('worldtravel', '0004_country_geometry'), + ] + + operations = [ + migrations.RemoveField( + model_name='country', + name='geometry', + ), + migrations.AddField( + model_name='region', + name='geometry', + field=django.contrib.gis.db.models.fields.MultiPolygonField(blank=True, null=True, srid=4326), + ), + ] diff --git a/backend/server/worldtravel/models.py b/backend/server/worldtravel/models.py index 8f4aca07..de0d87fe 100644 --- a/backend/server/worldtravel/models.py +++ b/backend/server/worldtravel/models.py @@ -1,6 +1,7 @@ from django.db import models from django.contrib.auth import get_user_model from django.core.exceptions import ValidationError +from django.contrib.gis.db import models as gis_models User = get_user_model() @@ -34,6 +35,7 @@ class Country(models.Model): choices=CONTINENT_CHOICES, default=AFRICA ) + class Meta: verbose_name = "Country" @@ -47,6 +49,7 @@ class Region(models.Model): name = models.CharField(max_length=100) name_en = models.CharField(max_length=100, blank=True, null=True) country = models.ForeignKey(Country, on_delete=models.CASCADE) + geometry = gis_models.MultiPolygonField(srid=4326, null=True, blank=True) def __str__(self): return self.name diff --git a/backend/server/worldtravel/serializers.py b/backend/server/worldtravel/serializers.py index 051a9b1f..a41ae70f 100644 --- a/backend/server/worldtravel/serializers.py +++ b/backend/server/worldtravel/serializers.py @@ -16,7 +16,7 @@ def get_flag_url(self, obj): class Meta: model = Country fields = '__all__' # Serialize all fields of the Adventure model - read_only_fields = ['id', 'name', 'country_code', 'continent', 'flag_url'] + read_only_fields = ['id', 'name', 'country_code', 'continent', 'flag_url', 'geometry'] class RegionSerializer(serializers.ModelSerializer): class Meta: diff --git a/backend/server/worldtravel/views.py b/backend/server/worldtravel/views.py index 1db56740..3bac6962 100644 --- a/backend/server/worldtravel/views.py +++ b/backend/server/worldtravel/views.py @@ -8,7 +8,10 @@ from rest_framework.decorators import api_view, permission_classes import os import json +from django.http import JsonResponse +from django.contrib.gis.geos import Point from django.conf import settings +from rest_framework.decorators import action from django.contrib.staticfiles import finders @api_view(['GET']) @@ -34,6 +37,19 @@ class CountryViewSet(viewsets.ReadOnlyModelViewSet): serializer_class = CountrySerializer permission_classes = [IsAuthenticated] + @action(detail=False, methods=['get']) + def check_point_in_region(self, request): + lat = float(request.query_params.get('lat')) + lon = float(request.query_params.get('lon')) + point = Point(lon, lat, srid=4326) + + region = Region.objects.filter(geometry__contains=point).first() + + if region: + return Response({'in_region': True, 'region_name': region.name}) + else: + return Response({'in_region': False}) + class RegionViewSet(viewsets.ReadOnlyModelViewSet): queryset = Region.objects.all() serializer_class = RegionSerializer From b11c3de46163f65ec3e4849f52943c57f07e9d19 Mon Sep 17 00:00:00 2001 From: Sean Morley Date: Fri, 23 Aug 2024 14:08:04 -0400 Subject: [PATCH 03/13] Auto mark regions --- backend/server/worldtravel/views.py | 2 +- .../src/lib/components/AdventureModal.svelte | 34 ++++++++++++++++++- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/backend/server/worldtravel/views.py b/backend/server/worldtravel/views.py index 3bac6962..af6e6a18 100644 --- a/backend/server/worldtravel/views.py +++ b/backend/server/worldtravel/views.py @@ -46,7 +46,7 @@ def check_point_in_region(self, request): region = Region.objects.filter(geometry__contains=point).first() if region: - return Response({'in_region': True, 'region_name': region.name}) + return Response({'in_region': True, 'region_name': region.name, 'region_id': region.id}) else: return Response({'in_region': False}) diff --git a/frontend/src/lib/components/AdventureModal.svelte b/frontend/src/lib/components/AdventureModal.svelte index ae4f0ddd..8df72b1e 100644 --- a/frontend/src/lib/components/AdventureModal.svelte +++ b/frontend/src/lib/components/AdventureModal.svelte @@ -29,6 +29,9 @@ let noPlaces: boolean = false; + let region_name: string | null = null; + let region_id: string | null = null; + let adventure: Adventure = { id: '', name: '', @@ -274,7 +277,7 @@ } } - function addMarker(e: CustomEvent) { + async function addMarker(e: CustomEvent) { markers = []; markers = [ ...markers, @@ -285,6 +288,19 @@ activity_type: '' } ]; + let res = await fetch( + `/api/countries/check_point_in_region/?lat=${e.detail.lngLat.lat}&lon=${e.detail.lngLat.lng}` + ); + let data = await res.json(); + if (data.error) { + addToast('error', data.error); + } else { + if (data.in_region) { + region_name = data.region_name; + region_id = data.region_id; + } + } + console.log(markers); } @@ -308,6 +324,19 @@ async function handleSubmit(event: Event) { event.preventDefault(); + if (region_id && region_name) { + let res = await fetch(`/api/visitedregion/`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ region: region_id }) + }); + if (res.ok) { + addToast('success', `Region ${region_name} marked as visited`); + } + } + if (adventure.date && adventure.end_date) { if (new Date(adventure.date) > new Date(adventure.end_date)) { addToast('error', 'Start date must be before end date'); @@ -655,6 +684,9 @@ it would also work to just use on:click on the MapLibre component itself. --> {/each} + {#if region_name} +

Region: {region_name} ({region_id})

+ {/if}
From f75c650a20782895183fe609fd16bdfba864882c Mon Sep 17 00:00:00 2001 From: Sean Morley Date: Fri, 23 Aug 2024 14:24:30 -0400 Subject: [PATCH 04/13] Add clear map function --- backend/server/worldtravel/serializers.py | 4 ++-- frontend/src/lib/components/AdventureModal.svelte | 13 +++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/backend/server/worldtravel/serializers.py b/backend/server/worldtravel/serializers.py index a41ae70f..6cb9c468 100644 --- a/backend/server/worldtravel/serializers.py +++ b/backend/server/worldtravel/serializers.py @@ -16,13 +16,13 @@ def get_flag_url(self, obj): class Meta: model = Country fields = '__all__' # Serialize all fields of the Adventure model - read_only_fields = ['id', 'name', 'country_code', 'continent', 'flag_url', 'geometry'] + read_only_fields = ['id', 'name', 'country_code', 'continent', 'flag_url'] class RegionSerializer(serializers.ModelSerializer): class Meta: model = Region fields = '__all__' # Serialize all fields of the Adventure model - read_only_fields = ['id', 'name', 'country', 'name_en'] + read_only_fields = ['id', 'name', 'country', 'name_en', 'geometry'] class VisitedRegionSerializer(serializers.ModelSerializer): class Meta: diff --git a/frontend/src/lib/components/AdventureModal.svelte b/frontend/src/lib/components/AdventureModal.svelte index 8df72b1e..c90ab65b 100644 --- a/frontend/src/lib/components/AdventureModal.svelte +++ b/frontend/src/lib/components/AdventureModal.svelte @@ -101,6 +101,13 @@ } } + function clearMap() { + console.log('CLEAR'); + markers = []; + region_id = null; + region_name = null; + } + let imageSearch: string = adventure.name || ''; async function removeImage(id: string) { @@ -298,6 +305,9 @@ if (data.in_region) { region_name = data.region_name; region_id = data.region_id; + } else { + region_id = null; + region_name = null; } } @@ -637,6 +647,9 @@ bind:value={query} /> +
{#if places.length > 0} From 43c134bf2b7e66e4329ef97c2917f4f1a5b59bbf Mon Sep 17 00:00:00 2001 From: Sean Morley Date: Fri, 23 Aug 2024 14:26:43 -0400 Subject: [PATCH 05/13] Add clear map function --- .../src/lib/components/AdventureModal.svelte | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/frontend/src/lib/components/AdventureModal.svelte b/frontend/src/lib/components/AdventureModal.svelte index c90ab65b..c4325d2e 100644 --- a/frontend/src/lib/components/AdventureModal.svelte +++ b/frontend/src/lib/components/AdventureModal.svelte @@ -295,20 +295,25 @@ activity_type: '' } ]; - let res = await fetch( - `/api/countries/check_point_in_region/?lat=${e.detail.lngLat.lat}&lon=${e.detail.lngLat.lng}` - ); - let data = await res.json(); - if (data.error) { - addToast('error', data.error); - } else { - if (data.in_region) { - region_name = data.region_name; - region_id = data.region_id; + if (adventure.type == 'visited') { + let res = await fetch( + `/api/countries/check_point_in_region/?lat=${e.detail.lngLat.lat}&lon=${e.detail.lngLat.lng}` + ); + let data = await res.json(); + if (data.error) { + addToast('error', data.error); } else { - region_id = null; - region_name = null; + if (data.in_region) { + region_name = data.region_name; + region_id = data.region_id; + } else { + region_id = null; + region_name = null; + } } + } else { + region_id = null; + region_name = null; } console.log(markers); From afe83bb03e60603931af30f4376d90e490b7110d Mon Sep 17 00:00:00 2001 From: Sean Morley Date: Fri, 23 Aug 2024 18:24:41 -0400 Subject: [PATCH 06/13] Add better support in adventure modal for the region search --- .../src/lib/components/AdventureModal.svelte | 47 +++++++++++++------ 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/frontend/src/lib/components/AdventureModal.svelte b/frontend/src/lib/components/AdventureModal.svelte index c4325d2e..c2e6e036 100644 --- a/frontend/src/lib/components/AdventureModal.svelte +++ b/frontend/src/lib/components/AdventureModal.svelte @@ -13,7 +13,7 @@ export let is_collection: boolean = false; import { DefaultMarker, MapEvents, MapLibre } from 'svelte-maplibre'; - let markers: Point[] = []; + let query: string = ''; let places: OpenStreetMapPlace[] = []; let images: { id: string; image: string }[] = []; @@ -72,6 +72,8 @@ collection: adventureToEdit?.collection || collection_id || null }; + let markers: Point[] = []; + let url: string = ''; let imageError: string = ''; let wikiImageError: string = ''; @@ -79,6 +81,7 @@ images = adventure.images || []; if (adventure.longitude && adventure.latitude) { + markers = []; markers = [ { lngLat: { lng: adventure.longitude, lat: adventure.latitude }, @@ -87,6 +90,7 @@ activity_type: '' } ]; + checkPointInRegion(); } if (longitude && latitude) { @@ -142,6 +146,13 @@ } } + $: { + if (adventure.type != 'visited') { + region_id = null; + region_name = null; + } + } + async function fetchImage() { let res = await fetch(url); let data = await res.blob(); @@ -247,6 +258,7 @@ activity_type: data[0]?.type || '' } ]; + checkPointInRegion(); } } console.log(data); @@ -284,21 +296,11 @@ } } - async function addMarker(e: CustomEvent) { - markers = []; - markers = [ - ...markers, - { - lngLat: e.detail.lngLat, - name: '', - location: '', - activity_type: '' - } - ]; + async function checkPointInRegion() { if (adventure.type == 'visited') { - let res = await fetch( - `/api/countries/check_point_in_region/?lat=${e.detail.lngLat.lat}&lon=${e.detail.lngLat.lng}` - ); + let lat = markers[0].lngLat.lat; + let lon = markers[0].lngLat.lng; + let res = await fetch(`/api/countries/check_point_in_region/?lat=${lat}&lon=${lon}`); let data = await res.json(); if (data.error) { addToast('error', data.error); @@ -315,6 +317,20 @@ region_id = null; region_name = null; } + } + + async function addMarker(e: CustomEvent) { + markers = []; + markers = [ + ...markers, + { + lngLat: e.detail.lngLat, + name: '', + location: '', + activity_type: '' + } + ]; + checkPointInRegion(); console.log(markers); } @@ -675,6 +691,7 @@ activity_type: place.type } ]; + checkPointInRegion(); }} > {place.display_name} From c13d6f4f21845f2930283d77b68c027600e180fb Mon Sep 17 00:00:00 2001 From: Sean Morley Date: Fri, 23 Aug 2024 18:34:11 -0400 Subject: [PATCH 07/13] Region check - api --- backend/server/worldtravel/views.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/backend/server/worldtravel/views.py b/backend/server/worldtravel/views.py index af6e6a18..3a79690c 100644 --- a/backend/server/worldtravel/views.py +++ b/backend/server/worldtravel/views.py @@ -13,6 +13,7 @@ from django.conf import settings from rest_framework.decorators import action from django.contrib.staticfiles import finders +from adventures.models import Adventure @api_view(['GET']) @permission_classes([IsAuthenticated]) @@ -49,6 +50,26 @@ def check_point_in_region(self, request): return Response({'in_region': True, 'region_name': region.name, 'region_id': region.id}) else: return Response({'in_region': False}) + + # make a post action that will get all of the users adventures and check if the point is in any of the regions if so make a visited region object for that user if it does not already exist + @action(detail=False, methods=['post']) + def region_check_all_adventures(self, request): + adventures = Adventure.objects.filter(user_id=request.user.id) + count = 0 + for adventure in adventures: + if adventure.latitude is not None and adventure.longitude is not None: + try: + print(f"Adventure {adventure.id}: lat={adventure.latitude}, lon={adventure.longitude}") + point = Point(float(adventure.longitude), float(adventure.latitude), srid=4326) + region = Region.objects.filter(geometry__contains=point).first() + if region: + if not VisitedRegion.objects.filter(user_id=request.user.id, region=region).exists(): + VisitedRegion.objects.create(user_id=request.user, region=region) + count += 1 + except Exception as e: + print(f"Error processing adventure {adventure.id}: {e}") + continue + return Response({'regions_visited': count}) class RegionViewSet(viewsets.ReadOnlyModelViewSet): queryset = Region.objects.all() From dab6efbe329d51754165c8b1c39ee509f19b1df7 Mon Sep 17 00:00:00 2001 From: Sean Morley Date: Fri, 23 Aug 2024 18:41:59 -0400 Subject: [PATCH 08/13] Region check - frontend --- backend/server/worldtravel/views.py | 2 +- frontend/src/routes/settings/+page.svelte | 25 +++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/backend/server/worldtravel/views.py b/backend/server/worldtravel/views.py index 3a79690c..1332b1ed 100644 --- a/backend/server/worldtravel/views.py +++ b/backend/server/worldtravel/views.py @@ -54,7 +54,7 @@ def check_point_in_region(self, request): # make a post action that will get all of the users adventures and check if the point is in any of the regions if so make a visited region object for that user if it does not already exist @action(detail=False, methods=['post']) def region_check_all_adventures(self, request): - adventures = Adventure.objects.filter(user_id=request.user.id) + adventures = Adventure.objects.filter(user_id=request.user.id, type='visited') count = 0 for adventure in adventures: if adventure.latitude is not None and adventure.longitude is not None: diff --git a/frontend/src/routes/settings/+page.svelte b/frontend/src/routes/settings/+page.svelte index 7ea46602..b67944f9 100644 --- a/frontend/src/routes/settings/+page.svelte +++ b/frontend/src/routes/settings/+page.svelte @@ -44,6 +44,21 @@ a.click(); URL.revokeObjectURL(url); } + + async function checkVisitedRegions() { + let res = await fetch('/api/countries/region_check_all_adventures/', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + } + }); + let data = await res.json(); + if (res.ok) { + addToast('success', `${data.regions_visited} regions updated`); + } else { + addToast('error', 'Error updating visited regions'); + } + }

Settings Page

@@ -157,6 +172,16 @@

This may take a few seconds...

+
+

Visited Region Check

+

+ By selecting this, the server will check all of your visited adventures and mark the regions + they are located in as "visited" in world travel. +

+ +

This may take longer depending on the number of adventures you have.

+
For Debug Use: Server PK={user.pk} | Date Joined: {user.date_joined From e489a7098005930a9ebc0c9e5c48b849b5726de1 Mon Sep 17 00:00:00 2001 From: Sean Morley Date: Fri, 23 Aug 2024 23:49:05 -0400 Subject: [PATCH 09/13] PostGIS migration for docker --- backend/Dockerfile | 2 +- backend/entrypoint.sh | 3 +++ backend/init-postgis.sql | 2 ++ .../management/commands/worldtravel-seed.py | 2 +- docker-compose.yml | 10 +++++----- frontend/src/routes/settings/+page.svelte | 17 ++++++++++------- 6 files changed, 22 insertions(+), 14 deletions(-) create mode 100644 backend/init-postgis.sql diff --git a/backend/Dockerfile b/backend/Dockerfile index eee89660..6de10676 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -13,7 +13,7 @@ WORKDIR /code # Install system dependencies RUN apt-get update \ - && apt-get install -y git postgresql-client \ + && apt-get install -y git postgresql-client gdal-bin libgdal-dev \ && apt-get clean # Install Python dependencies diff --git a/backend/entrypoint.sh b/backend/entrypoint.sh index 26fc0452..269a5aab 100644 --- a/backend/entrypoint.sh +++ b/backend/entrypoint.sh @@ -13,6 +13,9 @@ done >&2 echo "PostgreSQL is up - continuing..." +# run sql commands +# psql -h "$PGHOST" -U "$PGUSER" -d "$PGDATABASE" -f /app/backend/init-postgis.sql + # Apply Django migrations python manage.py migrate diff --git a/backend/init-postgis.sql b/backend/init-postgis.sql new file mode 100644 index 00000000..032f6f9f --- /dev/null +++ b/backend/init-postgis.sql @@ -0,0 +1,2 @@ +CREATE EXTENSION IF NOT EXISTS postgis; +CREATE EXTENSION IF NOT EXISTS postgis_topology; \ No newline at end of file diff --git a/backend/server/worldtravel/management/commands/worldtravel-seed.py b/backend/server/worldtravel/management/commands/worldtravel-seed.py index b63d97f6..f7ec9a41 100644 --- a/backend/server/worldtravel/management/commands/worldtravel-seed.py +++ b/backend/server/worldtravel/management/commands/worldtravel-seed.py @@ -19,7 +19,7 @@ def setGeometry(region_code): json_file = os.path.join('static/data', f'{country_code.lower()}.json') if not os.path.exists(json_file): - print(f'File {country_code}.json does not exist') + print(f'File {country_code}.json does not exist (it probably hasn''t been added, contributors are welcome!)') return None try: diff --git a/docker-compose.yml b/docker-compose.yml index 871e5d35..c7d2a4db 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,8 +2,8 @@ version: "3.9" services: web: - #build: ./frontend/ - image: ghcr.io/seanmorley15/adventurelog-frontend:latest + build: ./frontend/ + #image: ghcr.io/seanmorley15/adventurelog-frontend:latest environment: - PUBLIC_SERVER_URL=http://server:8000 # MOST DOCKER USERS WILL NEVER NEED TO CHANGE THIS, EVEN IF YOU CHANGE THE PORTS - ORIGIN=http://localhost:8080 @@ -14,7 +14,7 @@ services: - server db: - image: postgres:latest + image: postgis/postgis:15-3.3 environment: POSTGRES_DB: database POSTGRES_USER: adventure @@ -23,8 +23,8 @@ services: - postgres_data:/var/lib/postgresql/data/ server: - #build: ./backend/ - image: ghcr.io/seanmorley15/adventurelog-backend:latest + build: ./backend/ + #image: ghcr.io/seanmorley15/adventurelog-backend:latest environment: - PGHOST=db - PGDATABASE=database diff --git a/frontend/src/routes/settings/+page.svelte b/frontend/src/routes/settings/+page.svelte index b67944f9..cb7bd28d 100644 --- a/frontend/src/routes/settings/+page.svelte +++ b/frontend/src/routes/settings/+page.svelte @@ -167,22 +167,25 @@ -
-

Data Export

- -

This may take a few seconds...

-
-
+ +

Visited Region Check

By selecting this, the server will check all of your visited adventures and mark the regions they are located in as "visited" in world travel.

- Update Visited Regions

This may take longer depending on the number of adventures you have.

+
+

Data Export

+ +

This may take a few seconds...

+
+ For Debug Use: Server PK={user.pk} | Date Joined: {user.date_joined ? new Date(user.date_joined).toDateString() From ffde90e6f58dd024d2f6d7c622d4f18ef3f8605f Mon Sep 17 00:00:00 2001 From: Sean Morley Date: Fri, 23 Aug 2024 23:56:56 -0400 Subject: [PATCH 10/13] Clean up files after postgis migration --- backend/init-postgis.sql | 2 -- backup.sh | 2 ++ deploy.sh | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) delete mode 100644 backend/init-postgis.sql diff --git a/backend/init-postgis.sql b/backend/init-postgis.sql deleted file mode 100644 index 032f6f9f..00000000 --- a/backend/init-postgis.sql +++ /dev/null @@ -1,2 +0,0 @@ -CREATE EXTENSION IF NOT EXISTS postgis; -CREATE EXTENSION IF NOT EXISTS postgis_topology; \ No newline at end of file diff --git a/backup.sh b/backup.sh index d85a5f2a..69d5b120 100644 --- a/backup.sh +++ b/backup.sh @@ -1,3 +1,5 @@ +# This script will create a backup of the adventurelog_media volume and store it in the current directory as adventurelog-backup.tar.gz + docker run --rm \ -v adventurelog_adventurelog_media:/backup-volume \ -v "$(pwd)":/backup \ diff --git a/deploy.sh b/deploy.sh index 4844fb77..9548c7c2 100644 --- a/deploy.sh +++ b/deploy.sh @@ -1,3 +1,5 @@ +# This script is used to deploy the latest version of AdventureLog to the server. It pulls the latest version of the Docker images and starts the containers. It is a simple script that can be run on the server, possibly as a cron job, to keep the server up to date with the latest version of the application. + echo "Deploying latest version of AdventureLog" docker compose pull echo "Stating containers" From 8a77cd98bb7f171c9bb119e9f3f11f8d7fe47d0f Mon Sep 17 00:00:00 2001 From: Sean Morley Date: Sat, 24 Aug 2024 08:27:30 -0400 Subject: [PATCH 11/13] Fix map filtering --- frontend/src/routes/map/+page.svelte | 28 ++++++---------------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/frontend/src/routes/map/+page.svelte b/frontend/src/routes/map/+page.svelte index 1a0f484c..a1571983 100644 --- a/frontend/src/routes/map/+page.svelte +++ b/frontend/src/routes/map/+page.svelte @@ -19,21 +19,10 @@ let showVisited = true; let showPlanned = true; - $: { - if (!showVisited) { - markers = data.props.markers.filter((marker) => marker.type !== 'visited'); - } else { - const visitedMarkers = data.props.markers.filter((marker) => marker.type === 'visited'); - markers = [...markers, ...visitedMarkers]; - } - if (!showPlanned) { - markers = data.props.markers.filter((marker) => marker.type !== 'planned'); - } else { - const plannedMarkers = data.props.markers.filter((marker) => marker.type === 'planned'); - markers = [...markers, ...plannedMarkers]; - } - console.log(markers); - } + $: filteredMarkers = markers.filter( + (marker) => + (showVisited && marker.type === 'visited') || (showPlanned && marker.type === 'planned') + ); let newMarker = []; @@ -43,7 +32,6 @@ function addMarker(e) { newMarker = []; newMarker = [...newMarker, { lngLat: e.detail.lngLat, name: 'Marker 1' }]; - console.log(newMarker); newLongitude = e.detail.lngLat.lng; newLatitude = e.detail.lngLat.lat; } @@ -55,19 +43,15 @@ } function createNewAdventure(event) { - console.log(event.detail); - let newMarker = { lngLat: [event.detail.longitude, event.detail.latitude], name: event.detail.name, - type: 'planned' + type: event.detail.type }; markers = [...markers, newMarker]; clearMarkers(); - console.log(markers); createModalOpen = false; } - let visitedRegions = data.props.visitedRegions; let geoJSON = []; @@ -154,7 +138,7 @@ class="relative aspect-[9/16] max-h-[70vh] w-full sm:aspect-video sm:max-h-full" standardControls > - {#each markers as { lngLat, name, type }} + {#each filteredMarkers as { lngLat, name, type }} {#if type == 'visited'} Date: Sat, 24 Aug 2024 16:22:03 -0400 Subject: [PATCH 12/13] Fix docker compose --- docker-compose.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index c7d2a4db..393b8df7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,8 +2,8 @@ version: "3.9" services: web: - build: ./frontend/ - #image: ghcr.io/seanmorley15/adventurelog-frontend:latest + #build: ./frontend/ + image: ghcr.io/seanmorley15/adventurelog-frontend:latest environment: - PUBLIC_SERVER_URL=http://server:8000 # MOST DOCKER USERS WILL NEVER NEED TO CHANGE THIS, EVEN IF YOU CHANGE THE PORTS - ORIGIN=http://localhost:8080 @@ -23,8 +23,8 @@ services: - postgres_data:/var/lib/postgresql/data/ server: - build: ./backend/ - #image: ghcr.io/seanmorley15/adventurelog-backend:latest + #build: ./backend/ + image: ghcr.io/seanmorley15/adventurelog-backend:latest environment: - PGHOST=db - PGDATABASE=database From bed96b1a9ba8f0463a8b65c2e8c03717f649e764 Mon Sep 17 00:00:00 2001 From: Sean Morley Date: Sat, 24 Aug 2024 16:29:50 -0400 Subject: [PATCH 13/13] Add container names --- docker-compose.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 393b8df7..287a0076 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,6 +4,7 @@ services: web: #build: ./frontend/ image: ghcr.io/seanmorley15/adventurelog-frontend:latest + container_name: adventurelog-frontend environment: - PUBLIC_SERVER_URL=http://server:8000 # MOST DOCKER USERS WILL NEVER NEED TO CHANGE THIS, EVEN IF YOU CHANGE THE PORTS - ORIGIN=http://localhost:8080 @@ -15,6 +16,7 @@ services: db: image: postgis/postgis:15-3.3 + container_name: adventurelog-db environment: POSTGRES_DB: database POSTGRES_USER: adventure @@ -25,6 +27,7 @@ services: server: #build: ./backend/ image: ghcr.io/seanmorley15/adventurelog-backend:latest + container_name: adventurelog-backend environment: - PGHOST=db - PGDATABASE=database @@ -47,6 +50,7 @@ services: nginx: image: nginx:latest + container_name: adventurelog-nginx ports: - "81:80" volumes: