mirror of
https://github.com/seanmorley15/AdventureLog.git
synced 2025-07-19 04:49:37 +02:00
Merge pull request #275 from seanmorley15/development
Merge for v0.6.0 update
This commit is contained in:
commit
030cacf4b1
19 changed files with 267 additions and 39 deletions
|
@ -13,7 +13,7 @@ WORKDIR /code
|
||||||
|
|
||||||
# Install system dependencies
|
# Install system dependencies
|
||||||
RUN apt-get update \
|
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
|
&& apt-get clean
|
||||||
|
|
||||||
# Install Python dependencies
|
# Install Python dependencies
|
||||||
|
|
|
@ -13,6 +13,9 @@ done
|
||||||
|
|
||||||
>&2 echo "PostgreSQL is up - continuing..."
|
>&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
|
# Apply Django migrations
|
||||||
python manage.py migrate
|
python manage.py migrate
|
||||||
|
|
||||||
|
|
|
@ -58,7 +58,7 @@ INSTALLED_APPS = (
|
||||||
'adventures',
|
'adventures',
|
||||||
'worldtravel',
|
'worldtravel',
|
||||||
'users',
|
'users',
|
||||||
# 'django_apscheduler',
|
'django.contrib.gis',
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -101,7 +101,7 @@ SIMPLE_JWT = {
|
||||||
|
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
'default': {
|
||||||
'ENGINE': 'django.db.backends.postgresql',
|
'ENGINE': 'django.contrib.gis.db.backends.postgis',
|
||||||
'NAME': getenv('PGDATABASE'),
|
'NAME': getenv('PGDATABASE'),
|
||||||
'USER': getenv('PGUSER'),
|
'USER': getenv('PGUSER'),
|
||||||
'PASSWORD': getenv('PGPASSWORD'),
|
'PASSWORD': getenv('PGPASSWORD'),
|
||||||
|
@ -228,11 +228,9 @@ SWAGGER_SETTINGS = {
|
||||||
'LOGOUT_URL': 'logout',
|
'LOGOUT_URL': 'logout',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# For demo purposes only. Use a white list in the real world.
|
# For demo purposes only. Use a white list in the real world.
|
||||||
CORS_ORIGIN_ALLOW_ALL = True
|
CORS_ORIGIN_ALLOW_ALL = True
|
||||||
|
|
||||||
|
|
||||||
from os import getenv
|
from os import getenv
|
||||||
|
|
||||||
CSRF_TRUSTED_ORIGINS = [origin.strip() for origin in getenv('CSRF_TRUSTED_ORIGINS', 'http://localhost').split(',') if origin.strip()]
|
CSRF_TRUSTED_ORIGINS = [origin.strip() for origin in getenv('CSRF_TRUSTED_ORIGINS', 'http://localhost').split(',') if origin.strip()]
|
||||||
|
@ -261,6 +259,4 @@ LOGGING = {
|
||||||
'propagate': False,
|
'propagate': False,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
SCHEDULER_AUTOSTART = True
|
|
|
@ -11,4 +11,4 @@ psycopg2-binary
|
||||||
Pillow
|
Pillow
|
||||||
whitenoise
|
whitenoise
|
||||||
django-resized
|
django-resized
|
||||||
django-apscheduler
|
django-geojson
|
|
@ -4,7 +4,7 @@
|
||||||
{
|
{
|
||||||
"type": "Feature",
|
"type": "Feature",
|
||||||
"geometry": {
|
"geometry": {
|
||||||
"type": "Polygon",
|
"type": "MultiPolygon",
|
||||||
"coordinates": [
|
"coordinates": [
|
||||||
[
|
[
|
||||||
[1.9221462784913, 48.457599361977],
|
[1.9221462784913, 48.457599361977],
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
"ISOCODE": "MX-CMX"
|
"ISOCODE": "MX-CMX"
|
||||||
},
|
},
|
||||||
"geometry": {
|
"geometry": {
|
||||||
"type": "Polygon",
|
"type": "MultiPolygon",
|
||||||
"coordinates": [
|
"coordinates": [
|
||||||
[
|
[
|
||||||
[-99.111241, 19.561498],
|
[-99.111241, 19.561498],
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
"AWATER": 23736382213
|
"AWATER": 23736382213
|
||||||
},
|
},
|
||||||
"geometry": {
|
"geometry": {
|
||||||
"type": "Polygon",
|
"type": "MultiPolygon",
|
||||||
"coordinates": [
|
"coordinates": [
|
||||||
[
|
[
|
||||||
[-94.0430515276176, 32.6930299766656],
|
[-94.0430515276176, 32.6930299766656],
|
||||||
|
|
|
@ -4,11 +4,66 @@ from django.contrib.auth import get_user_model
|
||||||
import requests
|
import requests
|
||||||
from worldtravel.models import Country, Region
|
from worldtravel.models import Country, Region
|
||||||
from django.db import transaction
|
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
|
from django.conf import settings
|
||||||
|
|
||||||
media_root = settings.MEDIA_ROOT
|
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 (it probably hasn''t been added, contributors are welcome!)')
|
||||||
|
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):
|
def saveCountryFlag(country_code):
|
||||||
flags_dir = os.path.join(media_root, 'flags')
|
flags_dir = os.path.join(media_root, 'flags')
|
||||||
|
|
||||||
|
@ -616,7 +671,9 @@ class Command(BaseCommand):
|
||||||
)
|
)
|
||||||
if created:
|
if created:
|
||||||
self.stdout.write(f'Inserted {name} into worldtravel regions')
|
self.stdout.write(f'Inserted {name} into worldtravel regions')
|
||||||
|
setGeometry(id)
|
||||||
else:
|
else:
|
||||||
|
setGeometry(id)
|
||||||
self.stdout.write(f'Updated {name} in worldtravel regions')
|
self.stdout.write(f'Updated {name} in worldtravel regions')
|
||||||
|
|
||||||
def insert_countries(self, countries):
|
def insert_countries(self, countries):
|
||||||
|
@ -627,6 +684,7 @@ class Command(BaseCommand):
|
||||||
)
|
)
|
||||||
if created:
|
if created:
|
||||||
saveCountryFlag(country_code)
|
saveCountryFlag(country_code)
|
||||||
|
|
||||||
self.stdout.write(f'Inserted {name} into worldtravel countries')
|
self.stdout.write(f'Inserted {name} into worldtravel countries')
|
||||||
else:
|
else:
|
||||||
saveCountryFlag(country_code)
|
saveCountryFlag(country_code)
|
||||||
|
@ -641,5 +699,7 @@ class Command(BaseCommand):
|
||||||
)
|
)
|
||||||
if created:
|
if created:
|
||||||
self.stdout.write(f'Inserted {name} into worldtravel regions')
|
self.stdout.write(f'Inserted {name} into worldtravel regions')
|
||||||
|
setGeometry(id)
|
||||||
else:
|
else:
|
||||||
|
setGeometry(id)
|
||||||
self.stdout.write(f'{name} already exists in worldtravel regions')
|
self.stdout.write(f'{name} already exists in worldtravel regions')
|
|
@ -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),
|
||||||
|
),
|
||||||
|
]
|
|
@ -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),
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,6 +1,7 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.contrib.gis.db import models as gis_models
|
||||||
|
|
||||||
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
@ -34,6 +35,7 @@ class Country(models.Model):
|
||||||
choices=CONTINENT_CHOICES,
|
choices=CONTINENT_CHOICES,
|
||||||
default=AFRICA
|
default=AFRICA
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Country"
|
verbose_name = "Country"
|
||||||
|
@ -47,6 +49,7 @@ class Region(models.Model):
|
||||||
name = models.CharField(max_length=100)
|
name = models.CharField(max_length=100)
|
||||||
name_en = models.CharField(max_length=100, blank=True, null=True)
|
name_en = models.CharField(max_length=100, blank=True, null=True)
|
||||||
country = models.ForeignKey(Country, on_delete=models.CASCADE)
|
country = models.ForeignKey(Country, on_delete=models.CASCADE)
|
||||||
|
geometry = gis_models.MultiPolygonField(srid=4326, null=True, blank=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
|
@ -22,7 +22,7 @@ class RegionSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Region
|
model = Region
|
||||||
fields = '__all__' # Serialize all fields of the Adventure model
|
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 VisitedRegionSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
|
@ -8,8 +8,12 @@ from rest_framework.response import Response
|
||||||
from rest_framework.decorators import api_view, permission_classes
|
from rest_framework.decorators import api_view, permission_classes
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
|
from django.http import JsonResponse
|
||||||
|
from django.contrib.gis.geos import Point
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from rest_framework.decorators import action
|
||||||
from django.contrib.staticfiles import finders
|
from django.contrib.staticfiles import finders
|
||||||
|
from adventures.models import Adventure
|
||||||
|
|
||||||
@api_view(['GET'])
|
@api_view(['GET'])
|
||||||
@permission_classes([IsAuthenticated])
|
@permission_classes([IsAuthenticated])
|
||||||
|
@ -34,6 +38,39 @@ class CountryViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
serializer_class = CountrySerializer
|
serializer_class = CountrySerializer
|
||||||
permission_classes = [IsAuthenticated]
|
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, '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, type='visited')
|
||||||
|
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):
|
class RegionViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
queryset = Region.objects.all()
|
queryset = Region.objects.all()
|
||||||
serializer_class = RegionSerializer
|
serializer_class = RegionSerializer
|
||||||
|
|
|
@ -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 \
|
docker run --rm \
|
||||||
-v adventurelog_adventurelog_media:/backup-volume \
|
-v adventurelog_adventurelog_media:/backup-volume \
|
||||||
-v "$(pwd)":/backup \
|
-v "$(pwd)":/backup \
|
||||||
|
|
|
@ -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"
|
echo "Deploying latest version of AdventureLog"
|
||||||
docker compose pull
|
docker compose pull
|
||||||
echo "Stating containers"
|
echo "Stating containers"
|
||||||
|
|
|
@ -4,6 +4,7 @@ services:
|
||||||
web:
|
web:
|
||||||
#build: ./frontend/
|
#build: ./frontend/
|
||||||
image: ghcr.io/seanmorley15/adventurelog-frontend:latest
|
image: ghcr.io/seanmorley15/adventurelog-frontend:latest
|
||||||
|
container_name: adventurelog-frontend
|
||||||
environment:
|
environment:
|
||||||
- PUBLIC_SERVER_URL=http://server:8000 # MOST DOCKER USERS WILL NEVER NEED TO CHANGE THIS, EVEN IF YOU CHANGE THE PORTS
|
- 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
|
- ORIGIN=http://localhost:8080
|
||||||
|
@ -14,7 +15,8 @@ services:
|
||||||
- server
|
- server
|
||||||
|
|
||||||
db:
|
db:
|
||||||
image: postgres:latest
|
image: postgis/postgis:15-3.3
|
||||||
|
container_name: adventurelog-db
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_DB: database
|
POSTGRES_DB: database
|
||||||
POSTGRES_USER: adventure
|
POSTGRES_USER: adventure
|
||||||
|
@ -25,6 +27,7 @@ services:
|
||||||
server:
|
server:
|
||||||
#build: ./backend/
|
#build: ./backend/
|
||||||
image: ghcr.io/seanmorley15/adventurelog-backend:latest
|
image: ghcr.io/seanmorley15/adventurelog-backend:latest
|
||||||
|
container_name: adventurelog-backend
|
||||||
environment:
|
environment:
|
||||||
- PGHOST=db
|
- PGHOST=db
|
||||||
- PGDATABASE=database
|
- PGDATABASE=database
|
||||||
|
@ -47,6 +50,7 @@ services:
|
||||||
|
|
||||||
nginx:
|
nginx:
|
||||||
image: nginx:latest
|
image: nginx:latest
|
||||||
|
container_name: adventurelog-nginx
|
||||||
ports:
|
ports:
|
||||||
- "81:80"
|
- "81:80"
|
||||||
volumes:
|
volumes:
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
export let is_collection: boolean = false;
|
export let is_collection: boolean = false;
|
||||||
|
|
||||||
import { DefaultMarker, MapEvents, MapLibre } from 'svelte-maplibre';
|
import { DefaultMarker, MapEvents, MapLibre } from 'svelte-maplibre';
|
||||||
let markers: Point[] = [];
|
|
||||||
let query: string = '';
|
let query: string = '';
|
||||||
let places: OpenStreetMapPlace[] = [];
|
let places: OpenStreetMapPlace[] = [];
|
||||||
let images: { id: string; image: string }[] = [];
|
let images: { id: string; image: string }[] = [];
|
||||||
|
@ -29,6 +29,9 @@
|
||||||
|
|
||||||
let noPlaces: boolean = false;
|
let noPlaces: boolean = false;
|
||||||
|
|
||||||
|
let region_name: string | null = null;
|
||||||
|
let region_id: string | null = null;
|
||||||
|
|
||||||
let adventure: Adventure = {
|
let adventure: Adventure = {
|
||||||
id: '',
|
id: '',
|
||||||
name: '',
|
name: '',
|
||||||
|
@ -69,6 +72,8 @@
|
||||||
collection: adventureToEdit?.collection || collection_id || null
|
collection: adventureToEdit?.collection || collection_id || null
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let markers: Point[] = [];
|
||||||
|
|
||||||
let url: string = '';
|
let url: string = '';
|
||||||
let imageError: string = '';
|
let imageError: string = '';
|
||||||
let wikiImageError: string = '';
|
let wikiImageError: string = '';
|
||||||
|
@ -76,6 +81,7 @@
|
||||||
images = adventure.images || [];
|
images = adventure.images || [];
|
||||||
|
|
||||||
if (adventure.longitude && adventure.latitude) {
|
if (adventure.longitude && adventure.latitude) {
|
||||||
|
markers = [];
|
||||||
markers = [
|
markers = [
|
||||||
{
|
{
|
||||||
lngLat: { lng: adventure.longitude, lat: adventure.latitude },
|
lngLat: { lng: adventure.longitude, lat: adventure.latitude },
|
||||||
|
@ -84,6 +90,7 @@
|
||||||
activity_type: ''
|
activity_type: ''
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
checkPointInRegion();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (longitude && latitude) {
|
if (longitude && latitude) {
|
||||||
|
@ -98,6 +105,13 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clearMap() {
|
||||||
|
console.log('CLEAR');
|
||||||
|
markers = [];
|
||||||
|
region_id = null;
|
||||||
|
region_name = null;
|
||||||
|
}
|
||||||
|
|
||||||
let imageSearch: string = adventure.name || '';
|
let imageSearch: string = adventure.name || '';
|
||||||
|
|
||||||
async function removeImage(id: string) {
|
async function removeImage(id: string) {
|
||||||
|
@ -132,6 +146,13 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: {
|
||||||
|
if (adventure.type != 'visited') {
|
||||||
|
region_id = null;
|
||||||
|
region_name = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function fetchImage() {
|
async function fetchImage() {
|
||||||
let res = await fetch(url);
|
let res = await fetch(url);
|
||||||
let data = await res.blob();
|
let data = await res.blob();
|
||||||
|
@ -237,6 +258,7 @@
|
||||||
activity_type: data[0]?.type || ''
|
activity_type: data[0]?.type || ''
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
checkPointInRegion();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log(data);
|
console.log(data);
|
||||||
|
@ -274,7 +296,30 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function addMarker(e: CustomEvent<any>) {
|
async function checkPointInRegion() {
|
||||||
|
if (adventure.type == 'visited') {
|
||||||
|
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);
|
||||||
|
} else {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addMarker(e: CustomEvent<any>) {
|
||||||
markers = [];
|
markers = [];
|
||||||
markers = [
|
markers = [
|
||||||
...markers,
|
...markers,
|
||||||
|
@ -285,6 +330,8 @@
|
||||||
activity_type: ''
|
activity_type: ''
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
checkPointInRegion();
|
||||||
|
|
||||||
console.log(markers);
|
console.log(markers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -308,6 +355,19 @@
|
||||||
async function handleSubmit(event: Event) {
|
async function handleSubmit(event: Event) {
|
||||||
event.preventDefault();
|
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 (adventure.date && adventure.end_date) {
|
||||||
if (new Date(adventure.date) > new Date(adventure.end_date)) {
|
if (new Date(adventure.date) > new Date(adventure.end_date)) {
|
||||||
addToast('error', 'Start date must be before end date');
|
addToast('error', 'Start date must be before end date');
|
||||||
|
@ -608,6 +668,9 @@
|
||||||
bind:value={query}
|
bind:value={query}
|
||||||
/>
|
/>
|
||||||
<button class="btn btn-neutral -mt-1" type="submit">Search</button>
|
<button class="btn btn-neutral -mt-1" type="submit">Search</button>
|
||||||
|
<button class="btn btn-neutral -mt-1" type="button" on:click={clearMap}
|
||||||
|
>Clear Map</button
|
||||||
|
>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{#if places.length > 0}
|
{#if places.length > 0}
|
||||||
|
@ -628,6 +691,7 @@
|
||||||
activity_type: place.type
|
activity_type: place.type
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
checkPointInRegion();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{place.display_name}
|
{place.display_name}
|
||||||
|
@ -655,6 +719,9 @@ it would also work to just use on:click on the MapLibre component itself. -->
|
||||||
{/each}
|
{/each}
|
||||||
</MapLibre>
|
</MapLibre>
|
||||||
</div>
|
</div>
|
||||||
|
{#if region_name}
|
||||||
|
<p class="text-lg font-semibold mt-2">Region: {region_name} ({region_id})</p>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<button type="submit" class="btn btn-primary">Save & Next</button>
|
<button type="submit" class="btn btn-primary">Save & Next</button>
|
||||||
|
|
|
@ -19,21 +19,10 @@
|
||||||
let showVisited = true;
|
let showVisited = true;
|
||||||
let showPlanned = true;
|
let showPlanned = true;
|
||||||
|
|
||||||
$: {
|
$: filteredMarkers = markers.filter(
|
||||||
if (!showVisited) {
|
(marker) =>
|
||||||
markers = data.props.markers.filter((marker) => marker.type !== 'visited');
|
(showVisited && marker.type === 'visited') || (showPlanned && marker.type === 'planned')
|
||||||
} 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
let newMarker = [];
|
let newMarker = [];
|
||||||
|
|
||||||
|
@ -43,7 +32,6 @@
|
||||||
function addMarker(e) {
|
function addMarker(e) {
|
||||||
newMarker = [];
|
newMarker = [];
|
||||||
newMarker = [...newMarker, { lngLat: e.detail.lngLat, name: 'Marker 1' }];
|
newMarker = [...newMarker, { lngLat: e.detail.lngLat, name: 'Marker 1' }];
|
||||||
console.log(newMarker);
|
|
||||||
newLongitude = e.detail.lngLat.lng;
|
newLongitude = e.detail.lngLat.lng;
|
||||||
newLatitude = e.detail.lngLat.lat;
|
newLatitude = e.detail.lngLat.lat;
|
||||||
}
|
}
|
||||||
|
@ -55,19 +43,15 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function createNewAdventure(event) {
|
function createNewAdventure(event) {
|
||||||
console.log(event.detail);
|
|
||||||
|
|
||||||
let newMarker = {
|
let newMarker = {
|
||||||
lngLat: [event.detail.longitude, event.detail.latitude],
|
lngLat: [event.detail.longitude, event.detail.latitude],
|
||||||
name: event.detail.name,
|
name: event.detail.name,
|
||||||
type: 'planned'
|
type: event.detail.type
|
||||||
};
|
};
|
||||||
markers = [...markers, newMarker];
|
markers = [...markers, newMarker];
|
||||||
clearMarkers();
|
clearMarkers();
|
||||||
console.log(markers);
|
|
||||||
createModalOpen = false;
|
createModalOpen = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let visitedRegions = data.props.visitedRegions;
|
let visitedRegions = data.props.visitedRegions;
|
||||||
|
|
||||||
let geoJSON = [];
|
let geoJSON = [];
|
||||||
|
@ -154,7 +138,7 @@
|
||||||
class="relative aspect-[9/16] max-h-[70vh] w-full sm:aspect-video sm:max-h-full"
|
class="relative aspect-[9/16] max-h-[70vh] w-full sm:aspect-video sm:max-h-full"
|
||||||
standardControls
|
standardControls
|
||||||
>
|
>
|
||||||
{#each markers as { lngLat, name, type }}
|
{#each filteredMarkers as { lngLat, name, type }}
|
||||||
{#if type == 'visited'}
|
{#if type == 'visited'}
|
||||||
<Marker
|
<Marker
|
||||||
{lngLat}
|
{lngLat}
|
||||||
|
|
|
@ -44,6 +44,21 @@
|
||||||
a.click();
|
a.click();
|
||||||
URL.revokeObjectURL(url);
|
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');
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<h1 class="text-center font-extrabold text-4xl mb-6">Settings Page</h1>
|
<h1 class="text-center font-extrabold text-4xl mb-6">Settings Page</h1>
|
||||||
|
@ -152,7 +167,20 @@
|
||||||
<button class="py-2 px-4 btn btn-primary mt-2">Change Email</button>
|
<button class="py-2 px-4 btn btn-primary mt-2">Change Email</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col items-center">
|
|
||||||
|
<div class="flex flex-col items-center mt-4">
|
||||||
|
<h1 class="text-center font-extrabold text-xl mt-4 mb-2">Visited Region Check</h1>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
<button class="btn btn-neutral mt-2 mb-2" on:click={checkVisitedRegions}
|
||||||
|
>Update Visited Regions</button
|
||||||
|
>
|
||||||
|
<p>This may take longer depending on the number of adventures you have.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col items-center mt-4">
|
||||||
<h1 class="text-center font-extrabold text-xl mt-4 mb-2">Data Export</h1>
|
<h1 class="text-center font-extrabold text-xl mt-4 mb-2">Data Export</h1>
|
||||||
<button class="btn btn-neutral mb-4" on:click={exportAdventures}> Export to JSON </button>
|
<button class="btn btn-neutral mb-4" on:click={exportAdventures}> Export to JSON </button>
|
||||||
<p>This may take a few seconds...</p>
|
<p>This may take a few seconds...</p>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue