diff --git a/README.md b/README.md index 1491d6b..4a4c801 100644 --- a/README.md +++ b/README.md @@ -149,6 +149,7 @@ AdventureLog is licensed under the GNU General Public License v3.0. # Attribution - Logo Design by [redtechtiger](https://github.com/redtechtiger) +- WorldTravel Dataset [dr5hn/countries-states-cities-database](https://github.com/dr5hn/countries-states-cities-database) - [Mexico GEOJSON](https://cartographyvectors.com/map/784-mexico-with-states) - [Japan GEOJSON](https://cartographyvectors.com/map/361-japan) - [Ireland GEOJSON](https://cartographyvectors.com/map/1399-ireland-provinces) diff --git a/backend/entrypoint.sh b/backend/entrypoint.sh index 269a5aa..cbb40db 100644 --- a/backend/entrypoint.sh +++ b/backend/entrypoint.sh @@ -34,7 +34,7 @@ EOF fi # Sync the countries and world travel regions -python manage.py worldtravel-seed --force +python manage.py download-countries # Start Django server python manage.py runserver 0.0.0.0:8000 diff --git a/backend/server/adventures/admin.py b/backend/server/adventures/admin.py index ab8bfc6..70eedc9 100644 --- a/backend/server/adventures/admin.py +++ b/backend/server/adventures/admin.py @@ -21,8 +21,8 @@ class AdventureAdmin(admin.ModelAdmin): class CountryAdmin(admin.ModelAdmin): - list_display = ('name', 'country_code', 'continent', 'number_of_regions') - list_filter = ('continent', 'country_code') + list_display = ('name', 'country_code', 'number_of_regions') + list_filter = ('subregion',) def number_of_regions(self, obj): return Region.objects.filter(country=obj).count() @@ -32,6 +32,7 @@ class CountryAdmin(admin.ModelAdmin): class RegionAdmin(admin.ModelAdmin): list_display = ('name', 'country', 'number_of_visits') + list_filter = ('country',) # list_filter = ('country', 'number_of_visits') def number_of_visits(self, obj): diff --git a/backend/server/main/settings.py b/backend/server/main/settings.py index a885736..dab34e7 100644 --- a/backend/server/main/settings.py +++ b/backend/server/main/settings.py @@ -260,4 +260,6 @@ LOGGING = { 'propagate': False, }, }, -} \ No newline at end of file +} + +COUNTRY_REGION_JSON_VERSION = 'v2.4' \ No newline at end of file diff --git a/backend/server/worldtravel/management/commands/download-countries.py b/backend/server/worldtravel/management/commands/download-countries.py new file mode 100644 index 0000000..c9dff83 --- /dev/null +++ b/backend/server/worldtravel/management/commands/download-countries.py @@ -0,0 +1,147 @@ +import os +from django.core.management.base import BaseCommand +import requests +from worldtravel.models import Country, Region +from django.db import transaction +import json + +from django.conf import settings + +COUNTRY_REGION_JSON_VERSION = settings.COUNTRY_REGION_JSON_VERSION + +media_root = settings.MEDIA_ROOT + +def saveCountryFlag(country_code): + # For standards, use the lowercase country_code + country_code = country_code.lower() + flags_dir = os.path.join(media_root, 'flags') + + # Check if the flags directory exists, if not, create it + if not os.path.exists(flags_dir): + os.makedirs(flags_dir) + + # Check if the flag already exists in the media folder + flag_path = os.path.join(flags_dir, f'{country_code}.png') + if os.path.exists(flag_path): + print(f'Flag for {country_code} already exists') + return + + res = requests.get(f'https://flagcdn.com/h240/{country_code}.png'.lower()) + if res.status_code == 200: + with open(flag_path, 'wb') as f: + f.write(res.content) + print(f'Flag for {country_code} downloaded') + else: + print(f'Error downloading flag for {country_code}') + +class Command(BaseCommand): + help = 'Imports the world travel data' + + def handle(self, *args, **options): + countries_json_path = os.path.join(settings.MEDIA_ROOT, f'countries+regions-{COUNTRY_REGION_JSON_VERSION}.json') + if not os.path.exists(countries_json_path): + res = requests.get(f'https://raw.githubusercontent.com/dr5hn/countries-states-cities-database/{COUNTRY_REGION_JSON_VERSION}/countries%2Bstates.json') + if res.status_code == 200: + with open(countries_json_path, 'w') as f: + f.write(res.text) + else: + self.stdout.write(self.style.ERROR('Error downloading countries+regions.json')) + return + + with open(countries_json_path, 'r') as f: + data = json.load(f) + + with transaction.atomic(): + existing_countries = {country.country_code: country for country in Country.objects.all()} + existing_regions = {region.id: region for region in Region.objects.all()} + + countries_to_create = [] + regions_to_create = [] + countries_to_update = [] + regions_to_update = [] + + processed_country_codes = set() + processed_region_ids = set() + + for country in data: + country_code = country['iso2'] + country_name = country['name'] + country_subregion = country['subregion'] + country_capital = country['capital'] + + processed_country_codes.add(country_code) + + if country_code in existing_countries: + country_obj = existing_countries[country_code] + country_obj.name = country_name + country_obj.subregion = country_subregion + country_obj.capital = country_capital + countries_to_update.append(country_obj) + else: + country_obj = Country( + name=country_name, + country_code=country_code, + subregion=country_subregion, + capital=country_capital + ) + countries_to_create.append(country_obj) + + saveCountryFlag(country_code) + self.stdout.write(self.style.SUCCESS(f'Country {country_name} prepared')) + + if country['states']: + for state in country['states']: + name = state['name'] + state_id = f"{country_code}-{state['state_code']}" + latitude = round(float(state['latitude']), 6) if state['latitude'] else None + longitude = round(float(state['longitude']), 6) if state['longitude'] else None + + processed_region_ids.add(state_id) + + if state_id in existing_regions: + region_obj = existing_regions[state_id] + region_obj.name = name + region_obj.country = country_obj + region_obj.longitude = longitude + region_obj.latitude = latitude + regions_to_update.append(region_obj) + else: + region_obj = Region( + id=state_id, + name=name, + country=country_obj, + longitude=longitude, + latitude=latitude + ) + regions_to_create.append(region_obj) + self.stdout.write(self.style.SUCCESS(f'State {state_id} prepared')) + else: + state_id = f"{country_code}-00" + processed_region_ids.add(state_id) + if state_id in existing_regions: + region_obj = existing_regions[state_id] + region_obj.name = country_name + region_obj.country = country_obj + regions_to_update.append(region_obj) + else: + region_obj = Region( + id=state_id, + name=country_name, + country=country_obj + ) + regions_to_create.append(region_obj) + self.stdout.write(self.style.SUCCESS(f'Region {state_id} prepared for {country_name}')) + + # Bulk create new countries and regions + Country.objects.bulk_create(countries_to_create) + Region.objects.bulk_create(regions_to_create) + + # Bulk update existing countries and regions + Country.objects.bulk_update(countries_to_update, ['name', 'subregion', 'capital']) + Region.objects.bulk_update(regions_to_update, ['name', 'country', 'longitude', 'latitude']) + + # Delete countries and regions that are no longer in the data + Country.objects.exclude(country_code__in=processed_country_codes).delete() + Region.objects.exclude(id__in=processed_region_ids).delete() + + self.stdout.write(self.style.SUCCESS('All data imported successfully')) \ No newline at end of file diff --git a/backend/server/worldtravel/migrations/0006_remove_country_continent_country_subregion.py b/backend/server/worldtravel/migrations/0006_remove_country_continent_country_subregion.py new file mode 100644 index 0000000..cefae81 --- /dev/null +++ b/backend/server/worldtravel/migrations/0006_remove_country_continent_country_subregion.py @@ -0,0 +1,22 @@ +# Generated by Django 5.0.8 on 2024-09-11 02:16 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('worldtravel', '0005_remove_country_geometry_region_geometry'), + ] + + operations = [ + migrations.RemoveField( + model_name='country', + name='continent', + ), + migrations.AddField( + model_name='country', + name='subregion', + field=models.CharField(blank=True, max_length=100, null=True), + ), + ] diff --git a/backend/server/worldtravel/migrations/0007_remove_region_geometry_remove_region_name_en.py b/backend/server/worldtravel/migrations/0007_remove_region_geometry_remove_region_name_en.py new file mode 100644 index 0000000..742221d --- /dev/null +++ b/backend/server/worldtravel/migrations/0007_remove_region_geometry_remove_region_name_en.py @@ -0,0 +1,21 @@ +# Generated by Django 5.0.8 on 2024-09-11 02:20 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('worldtravel', '0006_remove_country_continent_country_subregion'), + ] + + operations = [ + migrations.RemoveField( + model_name='region', + name='geometry', + ), + migrations.RemoveField( + model_name='region', + name='name_en', + ), + ] diff --git a/backend/server/worldtravel/migrations/0008_region_latitude_region_longitude.py b/backend/server/worldtravel/migrations/0008_region_latitude_region_longitude.py new file mode 100644 index 0000000..bf6d06d --- /dev/null +++ b/backend/server/worldtravel/migrations/0008_region_latitude_region_longitude.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.8 on 2024-09-11 02:29 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('worldtravel', '0007_remove_region_geometry_remove_region_name_en'), + ] + + operations = [ + migrations.AddField( + model_name='region', + name='latitude', + field=models.DecimalField(blank=True, decimal_places=6, max_digits=9, null=True), + ), + migrations.AddField( + model_name='region', + name='longitude', + field=models.DecimalField(blank=True, decimal_places=6, max_digits=9, null=True), + ), + ] diff --git a/backend/server/worldtravel/migrations/0009_alter_country_country_code.py b/backend/server/worldtravel/migrations/0009_alter_country_country_code.py new file mode 100644 index 0000000..b09300d --- /dev/null +++ b/backend/server/worldtravel/migrations/0009_alter_country_country_code.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.8 on 2024-09-11 02:52 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('worldtravel', '0008_region_latitude_region_longitude'), + ] + + operations = [ + migrations.AlterField( + model_name='country', + name='country_code', + field=models.CharField(max_length=2, unique=True), + ), + ] diff --git a/backend/server/worldtravel/migrations/0010_country_capital.py b/backend/server/worldtravel/migrations/0010_country_capital.py new file mode 100644 index 0000000..35a2d70 --- /dev/null +++ b/backend/server/worldtravel/migrations/0010_country_capital.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.8 on 2024-09-11 19:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('worldtravel', '0009_alter_country_country_code'), + ] + + operations = [ + migrations.AddField( + model_name='country', + name='capital', + field=models.CharField(blank=True, max_length=100, null=True), + ), + ] diff --git a/backend/server/worldtravel/models.py b/backend/server/worldtravel/models.py index de0d87f..f7f8f99 100644 --- a/backend/server/worldtravel/models.py +++ b/backend/server/worldtravel/models.py @@ -9,33 +9,12 @@ User = get_user_model() default_user_id = 1 # Replace with an actual user ID class Country(models.Model): - AFRICA = 'AF' - ANTARCTICA = 'AN' - ASIA = 'AS' - EUROPE = 'EU' - NORTH_AMERICA = 'NA' - OCEANIA = 'OC' - SOUTH_AMERICA = 'SA' - - CONTINENT_CHOICES = [ - (AFRICA, 'Africa'), - (ANTARCTICA, 'Antarctica'), - (ASIA, 'Asia'), - (EUROPE, 'Europe'), - (NORTH_AMERICA, 'North America'), - (OCEANIA, 'Oceania'), - (SOUTH_AMERICA, 'South America'), - ] id = models.AutoField(primary_key=True) name = models.CharField(max_length=100) - country_code = models.CharField(max_length=2) - continent = models.CharField( - max_length=2, - choices=CONTINENT_CHOICES, - default=AFRICA - ) - + country_code = models.CharField(max_length=2, unique=True) #iso2 code + subregion = models.CharField(max_length=100, blank=True, null=True) + capital = models.CharField(max_length=100, blank=True, null=True) class Meta: verbose_name = "Country" @@ -47,9 +26,9 @@ class Country(models.Model): class Region(models.Model): id = models.CharField(primary_key=True) 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) + longitude = models.DecimalField(max_digits=9, decimal_places=6, null=True, blank=True) + latitude = models.DecimalField(max_digits=9, decimal_places=6, null=True, blank=True) def __str__(self): return self.name @@ -66,4 +45,4 @@ class VisitedRegion(models.Model): def save(self, *args, **kwargs): if VisitedRegion.objects.filter(user_id=self.user_id, region=self.region).exists(): raise ValidationError("Region already visited by user.") - super().save(*args, **kwargs) + super().save(*args, **kwargs) \ No newline at end of file diff --git a/backend/server/worldtravel/serializers.py b/backend/server/worldtravel/serializers.py index 6cb9c46..b6be995 100644 --- a/backend/server/worldtravel/serializers.py +++ b/backend/server/worldtravel/serializers.py @@ -2,7 +2,6 @@ import os from .models import Country, Region, VisitedRegion from rest_framework import serializers - class CountrySerializer(serializers.ModelSerializer): def get_public_url(self, obj): return os.environ.get('PUBLIC_URL', 'http://127.0.0.1:8000').rstrip('/').replace("'", "") @@ -11,21 +10,25 @@ class CountrySerializer(serializers.ModelSerializer): def get_flag_url(self, obj): public_url = self.get_public_url(obj) - return public_url + '/media/' + 'flags/' + obj.country_code + '.png' + return public_url + '/media/' + 'flags/' + obj.country_code.lower() + '.png' class Meta: model = Country - fields = '__all__' # Serialize all fields of the Adventure model - read_only_fields = ['id', 'name', 'country_code', 'continent', 'flag_url'] + fields = '__all__' + read_only_fields = ['id', 'name', 'country_code', 'subregion', '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', 'geometry'] + fields = '__all__' + read_only_fields = ['id', 'name', 'country', 'longitude', 'latitude'] class VisitedRegionSerializer(serializers.ModelSerializer): + longitude = serializers.DecimalField(source='region.longitude', max_digits=9, decimal_places=6, read_only=True) + latitude = serializers.DecimalField(source='region.latitude', max_digits=9, decimal_places=6, read_only=True) + name = serializers.CharField(source='region.name', read_only=True) + class Meta: model = VisitedRegion - fields = '__all__' # Serialize all fields of the Adventure model - read_only_fields = ['user_id', 'id'] \ No newline at end of file + fields = ['id', 'user_id', 'region', 'longitude', 'latitude', 'name'] + read_only_fields = ['user_id', 'id', 'longitude', 'latitude', 'name'] \ No newline at end of file diff --git a/backend/server/worldtravel/views.py b/backend/server/worldtravel/views.py index 1332b1e..ba4c3cc 100644 --- a/backend/server/worldtravel/views.py +++ b/backend/server/worldtravel/views.py @@ -34,7 +34,7 @@ def visits_by_country(request, country_code): return Response(serializer.data) class CountryViewSet(viewsets.ReadOnlyModelViewSet): - queryset = Country.objects.all() + queryset = Country.objects.all().order_by('name') serializer_class = CountrySerializer permission_classes = [IsAuthenticated] diff --git a/frontend/package.json b/frontend/package.json index 0104eed..5add01c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -19,6 +19,7 @@ "@sveltejs/kit": "^2.5.17", "@sveltejs/vite-plugin-svelte": "^3.1.1", "@tailwindcss/typography": "^0.5.13", + "@types/node": "^22.5.4", "autoprefixer": "^10.4.19", "daisyui": "^4.12.6", "postcss": "^8.4.38", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index eb003cb..55a4749 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -17,22 +17,25 @@ importers: version: 1.1.67 '@sveltejs/adapter-auto': specifier: ^3.2.2 - version: 3.2.2(@sveltejs/kit@2.5.17(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.3.1))(svelte@4.2.18)(vite@5.3.1)) + version: 3.2.2(@sveltejs/kit@2.5.17(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.3.1(@types/node@22.5.4)))(svelte@4.2.18)(vite@5.3.1(@types/node@22.5.4))) '@sveltejs/adapter-node': specifier: ^5.2.0 - version: 5.2.0(@sveltejs/kit@2.5.17(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.3.1))(svelte@4.2.18)(vite@5.3.1)) + version: 5.2.0(@sveltejs/kit@2.5.17(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.3.1(@types/node@22.5.4)))(svelte@4.2.18)(vite@5.3.1(@types/node@22.5.4))) '@sveltejs/adapter-vercel': specifier: ^5.4.1 - version: 5.4.1(@sveltejs/kit@2.5.17(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.3.1))(svelte@4.2.18)(vite@5.3.1)) + version: 5.4.1(@sveltejs/kit@2.5.17(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.3.1(@types/node@22.5.4)))(svelte@4.2.18)(vite@5.3.1(@types/node@22.5.4))) '@sveltejs/kit': specifier: ^2.5.17 - version: 2.5.17(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.3.1))(svelte@4.2.18)(vite@5.3.1) + version: 2.5.17(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.3.1(@types/node@22.5.4)))(svelte@4.2.18)(vite@5.3.1(@types/node@22.5.4)) '@sveltejs/vite-plugin-svelte': specifier: ^3.1.1 - version: 3.1.1(svelte@4.2.18)(vite@5.3.1) + version: 3.1.1(svelte@4.2.18)(vite@5.3.1(@types/node@22.5.4)) '@tailwindcss/typography': specifier: ^0.5.13 version: 0.5.13(tailwindcss@3.4.4) + '@types/node': + specifier: ^22.5.4 + version: 22.5.4 autoprefixer: specifier: ^10.4.19 version: 10.4.19(postcss@8.4.38) @@ -68,7 +71,7 @@ importers: version: 0.19.0 vite: specifier: ^5.3.1 - version: 5.3.1 + version: 5.3.1(@types/node@22.5.4) packages: @@ -501,6 +504,9 @@ packages: '@types/mapbox__vector-tile@1.3.4': resolution: {integrity: sha512-bpd8dRn9pr6xKvuEBQup8pwQfD4VUyqO/2deGjfpe6AwC8YRlyEipvefyRJUSiCJTZuCb8Pl1ciVV5ekqJ96Bg==} + '@types/node@22.5.4': + resolution: {integrity: sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==} + '@types/pbf@3.0.5': resolution: {integrity: sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA==} @@ -1634,6 +1640,9 @@ packages: ufo@1.5.3: resolution: {integrity: sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==} + undici-types@6.19.8: + resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + union-value@1.0.1: resolution: {integrity: sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==} engines: {node: '>=0.10.0'} @@ -2049,31 +2058,31 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.18.0': optional: true - '@sveltejs/adapter-auto@3.2.2(@sveltejs/kit@2.5.17(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.3.1))(svelte@4.2.18)(vite@5.3.1))': + '@sveltejs/adapter-auto@3.2.2(@sveltejs/kit@2.5.17(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.3.1(@types/node@22.5.4)))(svelte@4.2.18)(vite@5.3.1(@types/node@22.5.4)))': dependencies: - '@sveltejs/kit': 2.5.17(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.3.1))(svelte@4.2.18)(vite@5.3.1) + '@sveltejs/kit': 2.5.17(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.3.1(@types/node@22.5.4)))(svelte@4.2.18)(vite@5.3.1(@types/node@22.5.4)) import-meta-resolve: 4.1.0 - '@sveltejs/adapter-node@5.2.0(@sveltejs/kit@2.5.17(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.3.1))(svelte@4.2.18)(vite@5.3.1))': + '@sveltejs/adapter-node@5.2.0(@sveltejs/kit@2.5.17(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.3.1(@types/node@22.5.4)))(svelte@4.2.18)(vite@5.3.1(@types/node@22.5.4)))': dependencies: '@rollup/plugin-commonjs': 26.0.1(rollup@4.18.0) '@rollup/plugin-json': 6.1.0(rollup@4.18.0) '@rollup/plugin-node-resolve': 15.2.3(rollup@4.18.0) - '@sveltejs/kit': 2.5.17(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.3.1))(svelte@4.2.18)(vite@5.3.1) + '@sveltejs/kit': 2.5.17(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.3.1(@types/node@22.5.4)))(svelte@4.2.18)(vite@5.3.1(@types/node@22.5.4)) rollup: 4.18.0 - '@sveltejs/adapter-vercel@5.4.1(@sveltejs/kit@2.5.17(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.3.1))(svelte@4.2.18)(vite@5.3.1))': + '@sveltejs/adapter-vercel@5.4.1(@sveltejs/kit@2.5.17(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.3.1(@types/node@22.5.4)))(svelte@4.2.18)(vite@5.3.1(@types/node@22.5.4)))': dependencies: - '@sveltejs/kit': 2.5.17(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.3.1))(svelte@4.2.18)(vite@5.3.1) + '@sveltejs/kit': 2.5.17(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.3.1(@types/node@22.5.4)))(svelte@4.2.18)(vite@5.3.1(@types/node@22.5.4)) '@vercel/nft': 0.27.2 esbuild: 0.21.5 transitivePeerDependencies: - encoding - supports-color - '@sveltejs/kit@2.5.17(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.3.1))(svelte@4.2.18)(vite@5.3.1)': + '@sveltejs/kit@2.5.17(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.3.1(@types/node@22.5.4)))(svelte@4.2.18)(vite@5.3.1(@types/node@22.5.4))': dependencies: - '@sveltejs/vite-plugin-svelte': 3.1.1(svelte@4.2.18)(vite@5.3.1) + '@sveltejs/vite-plugin-svelte': 3.1.1(svelte@4.2.18)(vite@5.3.1(@types/node@22.5.4)) '@types/cookie': 0.6.0 cookie: 0.6.0 devalue: 5.0.0 @@ -2087,28 +2096,28 @@ snapshots: sirv: 2.0.4 svelte: 4.2.18 tiny-glob: 0.2.9 - vite: 5.3.1 + vite: 5.3.1(@types/node@22.5.4) - '@sveltejs/vite-plugin-svelte-inspector@2.1.0(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.3.1))(svelte@4.2.18)(vite@5.3.1)': + '@sveltejs/vite-plugin-svelte-inspector@2.1.0(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.3.1(@types/node@22.5.4)))(svelte@4.2.18)(vite@5.3.1(@types/node@22.5.4))': dependencies: - '@sveltejs/vite-plugin-svelte': 3.1.1(svelte@4.2.18)(vite@5.3.1) + '@sveltejs/vite-plugin-svelte': 3.1.1(svelte@4.2.18)(vite@5.3.1(@types/node@22.5.4)) debug: 4.3.5 svelte: 4.2.18 - vite: 5.3.1 + vite: 5.3.1(@types/node@22.5.4) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.3.1)': + '@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.3.1(@types/node@22.5.4))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 2.1.0(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.3.1))(svelte@4.2.18)(vite@5.3.1) + '@sveltejs/vite-plugin-svelte-inspector': 2.1.0(@sveltejs/vite-plugin-svelte@3.1.1(svelte@4.2.18)(vite@5.3.1(@types/node@22.5.4)))(svelte@4.2.18)(vite@5.3.1(@types/node@22.5.4)) debug: 4.3.5 deepmerge: 4.3.1 kleur: 4.1.5 magic-string: 0.30.10 svelte: 4.2.18 svelte-hmr: 0.16.0(svelte@4.2.18) - vite: 5.3.1 - vitefu: 0.2.5(vite@5.3.1) + vite: 5.3.1(@types/node@22.5.4) + vitefu: 0.2.5(vite@5.3.1(@types/node@22.5.4)) transitivePeerDependencies: - supports-color @@ -2144,6 +2153,10 @@ snapshots: '@types/mapbox__point-geometry': 0.1.4 '@types/pbf': 3.0.5 + '@types/node@22.5.4': + dependencies: + undici-types: 6.19.8 + '@types/pbf@3.0.5': {} '@types/pug@2.0.10': {} @@ -3270,6 +3283,8 @@ snapshots: ufo@1.5.3: {} + undici-types@6.19.8: {} + union-value@1.0.1: dependencies: arr-union: 3.1.0 @@ -3304,17 +3319,18 @@ snapshots: util-deprecate@1.0.2: {} - vite@5.3.1: + vite@5.3.1(@types/node@22.5.4): dependencies: esbuild: 0.21.5 postcss: 8.4.38 rollup: 4.18.0 optionalDependencies: + '@types/node': 22.5.4 fsevents: 2.3.3 - vitefu@0.2.5(vite@5.3.1): + vitefu@0.2.5(vite@5.3.1(@types/node@22.5.4)): optionalDependencies: - vite: 5.3.1 + vite: 5.3.1(@types/node@22.5.4) vt-pbf@3.1.3: dependencies: diff --git a/frontend/src/lib/components/CountryCard.svelte b/frontend/src/lib/components/CountryCard.svelte index 0fbdace..d75e3ed 100644 --- a/frontend/src/lib/components/CountryCard.svelte +++ b/frontend/src/lib/components/CountryCard.svelte @@ -5,6 +5,8 @@ import { createEventDispatcher } from 'svelte'; const dispatch = createEventDispatcher(); + import MapMarkerStar from '~icons/mdi/map-marker-star'; + export let country: Country; async function nav() { @@ -21,7 +23,12 @@
{region}
+