1
0
Fork 0
mirror of https://github.com/seanmorley15/AdventureLog.git synced 2025-07-23 06:49:37 +02:00

Rename Adventures to Locations (#696)

* Refactor user_id to user in adventures and related models, views, and components

- Updated all instances of user_id to user in the adventures app, including models, serializers, views, and frontend components.
- Adjusted queries and filters to reflect the new user field naming convention.
- Ensured consistency across the codebase for user identification in adventures, collections, notes, and transportation entities.
- Modified frontend components to align with the updated data structure, ensuring proper access control and rendering based on user ownership.

* Refactor adventure-related views and components to use "Location" terminology

- Updated GlobalSearchView to replace AdventureSerializer with LocationSerializer.
- Modified IcsCalendarGeneratorViewSet to use LocationSerializer instead of AdventureSerializer.
- Created new LocationImageViewSet for managing location images, including primary image toggling and image deletion.
- Introduced LocationViewSet for managing locations with enhanced filtering, sorting, and sharing capabilities.
- Updated ReverseGeocodeViewSet to utilize LocationSerializer.
- Added ActivityTypesView to retrieve distinct activity types from locations.
- Refactored user views to replace AdventureSerializer with LocationSerializer.
- Updated frontend components to reflect changes from "adventure" to "location", including AdventureCard, AdventureLink, AdventureModal, and others.
- Adjusted API endpoints in frontend routes to align with new location-based structure.
- Ensured all references to adventures are replaced with locations across the codebase.

* refactor: rename adventures to locations across the application

- Updated localization files to replace adventure-related terms with location-related terms.
- Refactored TypeScript types and variables from Adventure to Location in various routes and components.
- Adjusted UI elements and labels to reflect the change from adventures to locations.
- Ensured all references to adventures in the codebase are consistent with the new location terminology.

* Refactor code structure for improved readability and maintainability

* feat: Implement location details page with server-side loading and deletion functionality

- Added +page.server.ts to handle server-side loading of additional location info.
- Created +page.svelte for displaying location details, including images, visits, and maps.
- Integrated GPX file handling and rendering on the map.
- Updated map route to link to locations instead of adventures.
- Refactored profile and search routes to use LocationCard instead of AdventureCard.

* docs: Update terminology from "Adventure" to "Location" and enhance project overview

* docs: Clarify collection examples in usage documentation

* feat: Enable credentials for GPX file fetch and add CORS_ALLOW_CREDENTIALS setting

* Refactor adventure references to locations across the backend and frontend

- Updated CategoryViewSet to reflect location context instead of adventures.
- Modified ChecklistViewSet to include locations in retrieval logic.
- Changed GlobalSearchView to search for locations instead of adventures.
- Adjusted IcsCalendarGeneratorViewSet to handle locations instead of adventures.
- Refactored LocationImageViewSet to remove unused import.
- Updated LocationViewSet to clarify public access for locations.
- Changed LodgingViewSet to reference locations instead of adventures.
- Modified NoteViewSet to prevent listing all locations.
- Updated RecommendationsViewSet to handle locations in parsing and response.
- Adjusted ReverseGeocodeViewSet to search through user locations.
- Updated StatsViewSet to count locations instead of adventures.
- Changed TagsView to reflect activity types for locations.
- Updated TransportationViewSet to reference locations instead of adventures.
- Added new translations for search results related to locations in multiple languages.
- Updated dashboard and profile pages to reflect location counts instead of adventure counts.
- Adjusted search routes to handle locations instead of adventures.

* Update banner image

* style: Update stats component background and border for improved visibility

* refactor: Rename AdventureCard and AdventureModal to LocationCard and LocationModal for consistency
This commit is contained in:
Sean Morley 2025-06-25 11:49:34 -04:00 committed by GitHub
parent 5308ec21d6
commit 493a13995c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
115 changed files with 3148 additions and 2759 deletions

View file

@ -6,7 +6,9 @@ Were excited to have you contribute to AdventureLog! To ensure that this comm
1. **Open an Issue First**: Discuss any changes or features you plan to implement by opening an issue. This helps to clarify your idea and ensures theres a shared understanding. 1. **Open an Issue First**: Discuss any changes or features you plan to implement by opening an issue. This helps to clarify your idea and ensures theres a shared understanding.
2. **Document Changes**: If your changes impact the user interface, add new environment variables, or introduce new container configurations, make sure to update the documentation accordingly. The documentation is located in the `documentation` folder. 2. **Document Changes**: If your changes impact the user interface, add new environment variables, or introduce new container configurations, make sure to update the documentation accordingly. The documentation is located in the `documentation` folder.
3. **Pull Request**: Submit a pull request with your changes. Make sure to reference the issue you opened in the description. 3. **Pull Request**: Submit a pull request with your changes directed towards the `development` branch. Make sure to reference the issue you opened in the description. After your pull request is submitted, it will be reviewed by the maintainers.
4. **Review Process**: The maintainers will review your pull request. They may suggest changes or improvements. Please be open to feedback and ready to make adjustments as needed.
5. **Merge**: Once your pull request is approved, it will be merged into the `development` branch. This branch is where all new features and changes are integrated before being released to the main branch.
## Code of Conduct ## Code of Conduct

View file

@ -38,7 +38,7 @@
## ⭐ About the Project ## ⭐ About the Project
Starting from a simple idea of tracking travel locations (called adventures), AdventureLog has grown into a full-fledged travel companion. With AdventureLog, you can log your adventures, keep track of where you've been on the world map, plan your next trip collaboratively, and share your experiences with friends and family. Starting from a simple idea of tracking travel locations, AdventureLog has grown into a full-fledged travel companion. With AdventureLog, you can log your adventures, keep track of where you've been on the world map, plan your next trip collaboratively, and share your experiences with friends and family.
AdventureLog was created to solve a problem: the lack of a modern, open-source, user-friendly travel companion. Many existing travel apps are either too complex, too expensive, or too closed-off to be useful for the average traveler. AdventureLog aims to be the opposite: simple, beautiful, and open to everyone. AdventureLog was created to solve a problem: the lack of a modern, open-source, user-friendly travel companion. Many existing travel apps are either too complex, too expensive, or too closed-off to be useful for the average traveler. AdventureLog aims to be the opposite: simple, beautiful, and open to everyone.

32
adventurelog_overview.md Normal file
View file

@ -0,0 +1,32 @@
# About AdventureLog
Starting from a /locations, AdventureLog has grown into a full-fledged travel companion. With AdventureLog, you can log your adventures, keep track of where you've been on the world map, plan your next trip collaboratively, and share your experiences with friends and family. **AdventureLog is the ultimate travel companion for the modern-day explorer**.
## Features
- **Track Your Adventures** 🌍: Log your adventures and keep track of where you've been on the world map.
- Adventures can store a variety of information, including the location, date, and description.
- Adventures can be sorted into custom categories for easy organization.
- Adventures can be marked as private or public, allowing you to share your adventures with friends and family.
- Keep track of the countries and regions you've visited with the world travel book.
- **Plan Your Next Trip** 📃: Take the guesswork out of planning your next adventure with an easy-to-use itinerary planner.
- Itineraries can be created for any number of days and can include multiple destinations.
- Itineraries include many planning features like flight information, notes, checklists, and links to external resources.
- Itineraries can be shared with friends and family for collaborative planning.
- **Share Your Experiences** 📸: Share your adventures with friends and family and collaborate on trips together.
- Adventures and itineraries can be shared via a public link or directly with other AdventureLog users.
- Collaborators can view and edit shared itineraries (collections), making planning a breeze.
## Why AdventureLog?
AdventureLog was created to solve a problem: the lack of a modern, open-source, user-friendly travel companion. Many existing travel apps are either too complex, too expensive, or too closed-off to be useful for the average traveler. AdventureLog aims to be the opposite: simple, beautiful, and open to everyone.
### Open Source (GPL-3.0)
AdventureLog is open-source software, licensed under the GPL-3.0 license. This means that you are free to use, modify, and distribute AdventureLog as you see fit. The source code is available on GitHub, and contributions are welcome from anyone who wants to help improve the project.
## About the Maintainer
Hi, I'm [Sean Morley](https://seanmorley.com), the creator of AdventureLog. I'm an Electrical Engineering student at the University of Connecticut, and I'm passionate about open-source software and building modern tools that help people solve real-world problems. I created AdventureLog to solve a problem: the lack of a modern, open-source, user-friendly travel companion. Many existing travel apps are either too complex, too expensive, or too closed-off to be useful for the average traveler. AdventureLog aims to be the opposite: simple, beautiful, and open to everyone.
I hope you enjoy using AdventureLog as much as I enjoy creating it! If you have any questions, feedback, or suggestions, feel free to reach out to me via the email address listed on my website. I'm always happy to hear from users and help in any way I can. Thank you for using AdventureLog, and happy travels! 🌍

View file

@ -1,7 +1,7 @@
import os import os
from django.contrib import admin from django.contrib import admin
from django.utils.html import mark_safe from django.utils.html import mark_safe
from .models import Adventure, Checklist, ChecklistItem, Collection, Transportation, Note, AdventureImage, Visit, Category, Attachment, Lodging from .models import Location, Checklist, ChecklistItem, Collection, Transportation, Note, LocationImage, Visit, Category, Attachment, Lodging
from worldtravel.models import Country, Region, VisitedRegion, City, VisitedCity from worldtravel.models import Country, Region, VisitedRegion, City, VisitedCity
from allauth.account.decorators import secure_admin_login from allauth.account.decorators import secure_admin_login
@ -11,19 +11,19 @@ admin.site.login = secure_admin_login(admin.site.login)
@admin.action(description="Trigger geocoding") @admin.action(description="Trigger geocoding")
def trigger_geocoding(modeladmin, request, queryset): def trigger_geocoding(modeladmin, request, queryset):
count = 0 count = 0
for adventure in queryset: for location in queryset:
try: try:
adventure.save() # Triggers geocoding logic in your model location.save() # Triggers geocoding logic in your model
count += 1 count += 1
except Exception as e: except Exception as e:
modeladmin.message_user(request, f"Error geocoding {adventure}: {e}", level='error') modeladmin.message_user(request, f"Error geocoding {location}: {e}", level='error')
modeladmin.message_user(request, f"Geocoding triggered for {count} adventures.", level='success') modeladmin.message_user(request, f"Geocoding triggered for {count} locations.", level='success')
class AdventureAdmin(admin.ModelAdmin): class LocationAdmin(admin.ModelAdmin):
list_display = ('name', 'get_category', 'get_visit_count', 'user_id', 'is_public') list_display = ('name', 'get_category', 'get_visit_count', 'user', 'is_public')
list_filter = ( 'user_id', 'is_public') list_filter = ( 'user', 'is_public')
search_fields = ('name',) search_fields = ('name',)
readonly_fields = ('city', 'region', 'country') readonly_fields = ('city', 'region', 'country')
actions = [trigger_geocoding] actions = [trigger_geocoding]
@ -96,8 +96,8 @@ class CustomUserAdmin(UserAdmin):
else: else:
return return
class AdventureImageAdmin(admin.ModelAdmin): class LocationImageAdmin(admin.ModelAdmin):
list_display = ('user_id', 'image_display') list_display = ('user', 'image_display')
def image_display(self, obj): def image_display(self, obj):
if obj.image: if obj.image:
@ -109,7 +109,7 @@ class AdventureImageAdmin(admin.ModelAdmin):
class VisitAdmin(admin.ModelAdmin): class VisitAdmin(admin.ModelAdmin):
list_display = ('adventure', 'start_date', 'end_date', 'notes') list_display = ('location', 'start_date', 'end_date', 'notes')
list_filter = ('start_date', 'end_date') list_filter = ('start_date', 'end_date')
search_fields = ('notes',) search_fields = ('notes',)
@ -125,19 +125,19 @@ class VisitAdmin(admin.ModelAdmin):
image_display.short_description = 'Image Preview' image_display.short_description = 'Image Preview'
class CategoryAdmin(admin.ModelAdmin): class CategoryAdmin(admin.ModelAdmin):
list_display = ('name', 'user_id', 'display_name', 'icon') list_display = ('name', 'user', 'display_name', 'icon')
search_fields = ('name', 'display_name') search_fields = ('name', 'display_name')
class CollectionAdmin(admin.ModelAdmin): class CollectionAdmin(admin.ModelAdmin):
list_display = ('name', 'user_id', 'is_public') list_display = ('name', 'user', 'is_public')
admin.site.register(CustomUser, CustomUserAdmin) admin.site.register(CustomUser, CustomUserAdmin)
admin.site.register(Adventure, AdventureAdmin) admin.site.register(Location, LocationAdmin)
admin.site.register(Collection, CollectionAdmin) admin.site.register(Collection, CollectionAdmin)
admin.site.register(Visit, VisitAdmin) admin.site.register(Visit, VisitAdmin)
admin.site.register(Country, CountryAdmin) admin.site.register(Country, CountryAdmin)
@ -147,7 +147,7 @@ admin.site.register(Transportation)
admin.site.register(Note) admin.site.register(Note)
admin.site.register(Checklist) admin.site.register(Checklist)
admin.site.register(ChecklistItem) admin.site.register(ChecklistItem)
admin.site.register(AdventureImage, AdventureImageAdmin) admin.site.register(LocationImage, LocationImageAdmin)
admin.site.register(Category, CategoryAdmin) admin.site.register(Category, CategoryAdmin)
admin.site.register(City, CityAdmin) admin.site.register(City, CityAdmin)
admin.site.register(VisitedCity) admin.site.register(VisitedCity)

View file

@ -167,7 +167,7 @@ def extractIsoCode(user, data):
return {"error": "No region found"} return {"error": "No region found"}
region = Region.objects.filter(id=iso_code).first() region = Region.objects.filter(id=iso_code).first()
visited_region = VisitedRegion.objects.filter(region=region, user_id=user).first() visited_region = VisitedRegion.objects.filter(region=region, user=user).first()
region_visited = False region_visited = False
city_visited = False city_visited = False
@ -177,7 +177,7 @@ def extractIsoCode(user, data):
if town_city_or_county: if town_city_or_county:
display_name = f"{town_city_or_county}, {region.name}, {country_code}" display_name = f"{town_city_or_county}, {region.name}, {country_code}"
city = City.objects.filter(name__contains=town_city_or_county, region=region).first() city = City.objects.filter(name__contains=town_city_or_county, region=region).first()
visited_city = VisitedCity.objects.filter(city=city, user_id=user).first() visited_city = VisitedCity.objects.filter(city=city, user=user).first()
if visited_region: if visited_region:
region_visited = True region_visited = True

View file

@ -2,7 +2,7 @@
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from adventures.models import Adventure from adventures.models import Location
class Command(BaseCommand): class Command(BaseCommand):
@ -38,8 +38,8 @@ class Command(BaseCommand):
] ]
for name, location, type_ in adventures: for name, location, type_ in adventures:
Adventure.objects.create( Location.objects.create(
user_id=user, user=user,
name=name, name=name,
location=location, location=location,
type=type_, type=type_,

View file

@ -1,12 +1,12 @@
from django.db import models from django.db import models
from django.db.models import Q from django.db.models import Q
class AdventureManager(models.Manager): class LocationManager(models.Manager):
def retrieve_adventures(self, user, include_owned=False, include_shared=False, include_public=False): def retrieve_locations(self, user, include_owned=False, include_shared=False, include_public=False):
query = Q() query = Q()
if include_owned: if include_owned:
query |= Q(user_id=user) query |= Q(user=user)
if include_shared: if include_shared:
query |= Q(collections__shared_with=user) query |= Q(collections__shared_with=user)

View file

@ -0,0 +1,91 @@
# Generated by Django 5.2.1 on 2025-06-19 20:29
from django.conf import settings
from django.db import migrations
class Migration(migrations.Migration):
replaces = [('adventures', '0036_rename_adventure_location'), ('adventures', '0037_rename_adventure_visit_location'), ('adventures', '0038_rename_adventureimage_locationimage'), ('adventures', '0039_rename_adventure_locationimage_location'), ('adventures', '0040_rename_adventure_attachment_location'), ('adventures', '0041_rename_user_id_location_user'), ('adventures', '0042_rename_user_id_locationimage_user'), ('adventures', '0043_rename_user_id_attachment_user'), ('adventures', '0044_rename_user_id_collection_user'), ('adventures', '0045_rename_user_id_transportation_user'), ('adventures', '0046_rename_user_id_note_user'), ('adventures', '0047_rename_user_id_checklist_user'), ('adventures', '0048_rename_user_id_checklistitem_user'), ('adventures', '0049_rename_user_id_category_user'), ('adventures', '0050_rename_user_id_lodging_user')]
dependencies = [
('adventures', '0035_remove_adventure_collection_adventure_collections'),
('worldtravel', '0016_remove_city_insert_id_remove_country_insert_id_and_more'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.RenameModel(
old_name='Adventure',
new_name='Location',
),
migrations.RenameField(
model_name='visit',
old_name='adventure',
new_name='location',
),
migrations.RenameModel(
old_name='AdventureImage',
new_name='LocationImage',
),
migrations.RenameField(
model_name='locationimage',
old_name='adventure',
new_name='location',
),
migrations.RenameField(
model_name='attachment',
old_name='adventure',
new_name='location',
),
migrations.RenameField(
model_name='location',
old_name='user_id',
new_name='user',
),
migrations.RenameField(
model_name='locationimage',
old_name='user_id',
new_name='user',
),
migrations.RenameField(
model_name='attachment',
old_name='user_id',
new_name='user',
),
migrations.RenameField(
model_name='collection',
old_name='user_id',
new_name='user',
),
migrations.RenameField(
model_name='transportation',
old_name='user_id',
new_name='user',
),
migrations.RenameField(
model_name='note',
old_name='user_id',
new_name='user',
),
migrations.RenameField(
model_name='checklist',
old_name='user_id',
new_name='user',
),
migrations.RenameField(
model_name='checklistitem',
old_name='user_id',
new_name='user',
),
migrations.RenameField(
model_name='category',
old_name='user_id',
new_name='user',
),
migrations.RenameField(
model_name='lodging',
old_name='user_id',
new_name='user',
),
]

View file

@ -0,0 +1,23 @@
# Generated by Django 5.2.1 on 2025-06-20 15:28
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('adventures', '0036_rename_adventure_location_squashed_0050_rename_user_id_lodging_user'),
]
operations = [
migrations.RenameField(
model_name='location',
old_name='activity_types',
new_name='tags',
),
migrations.AlterField(
model_name='location',
name='collections',
field=models.ManyToManyField(blank=True, related_name='locations', to='adventures.collection'),
),
]

View file

@ -1,10 +1,9 @@
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
import os import os
from typing import Iterable
import uuid import uuid
from django.db import models from django.db import models
from django.utils.deconstruct import deconstructible from django.utils.deconstruct import deconstructible
from adventures.managers import AdventureManager from adventures.managers import LocationManager
import threading import threading
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.postgres.fields import ArrayField from django.contrib.postgres.fields import ArrayField
@ -13,46 +12,45 @@ from django_resized import ResizedImageField
from worldtravel.models import City, Country, Region, VisitedCity, VisitedRegion from worldtravel.models import City, Country, Region, VisitedCity, VisitedRegion
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.utils import timezone from django.utils import timezone
from adventures.utils.timezones import TIMEZONES
def background_geocode_and_assign(adventure_id: str): def background_geocode_and_assign(location_id: str):
print(f"[Adventure Geocode Thread] Starting geocode for adventure {adventure_id}") print(f"[Location Geocode Thread] Starting geocode for location {location_id}")
try: try:
adventure = Adventure.objects.get(id=adventure_id) location = Location.objects.get(id=location_id)
if not (adventure.latitude and adventure.longitude): if not (location.latitude and location.longitude):
return return
from adventures.geocoding import reverse_geocode # or wherever you defined it from adventures.geocoding import reverse_geocode # or wherever you defined it
is_visited = adventure.is_visited_status() is_visited = location.is_visited_status()
result = reverse_geocode(adventure.latitude, adventure.longitude, adventure.user_id) result = reverse_geocode(location.latitude, location.longitude, location.user)
if 'region_id' in result: if 'region_id' in result:
region = Region.objects.filter(id=result['region_id']).first() region = Region.objects.filter(id=result['region_id']).first()
if region: if region:
adventure.region = region location.region = region
if is_visited: if is_visited:
VisitedRegion.objects.get_or_create(user_id=adventure.user_id, region=region) VisitedRegion.objects.get_or_create(user=location.user, region=region)
if 'city_id' in result: if 'city_id' in result:
city = City.objects.filter(id=result['city_id']).first() city = City.objects.filter(id=result['city_id']).first()
if city: if city:
adventure.city = city location.city = city
if is_visited: if is_visited:
VisitedCity.objects.get_or_create(user_id=adventure.user_id, city=city) VisitedCity.objects.get_or_create(user=location.user, city=city)
if 'country_id' in result: if 'country_id' in result:
country = Country.objects.filter(country_code=result['country_id']).first() country = Country.objects.filter(country_code=result['country_id']).first()
if country: if country:
adventure.country = country location.country = country
# Save updated location info # Save updated location info
# Save updated location info, skip geocode threading # Save updated location info, skip geocode threading
adventure.save(update_fields=["region", "city", "country"], _skip_geocode=True) location.save(update_fields=["region", "city", "country"], _skip_geocode=True)
# print(f"[Adventure Geocode Thread] Successfully processed {adventure_id}: {adventure.name} - {adventure.latitude}, {adventure.longitude}")
except Exception as e: except Exception as e:
# Optional: log or print the error # Optional: log or print the error
print(f"[Adventure Geocode Thread] Error processing {adventure_id}: {e}") print(f"[Location Geocode Thread] Error processing {location_id}: {e}")
def validate_file_extension(value): def validate_file_extension(value):
import os import os
@ -62,6 +60,7 @@ def validate_file_extension(value):
if not ext.lower() in valid_extensions: if not ext.lower() in valid_extensions:
raise ValidationError('Unsupported file extension.') raise ValidationError('Unsupported file extension.')
# Legacy support for old adventure types, not used in newer versions since custom categories are now used
ADVENTURE_TYPES = [ ADVENTURE_TYPES = [
('general', 'General 🌍'), ('general', 'General 🌍'),
('outdoor', 'Outdoor 🏞️'), ('outdoor', 'Outdoor 🏞️'),
@ -87,426 +86,6 @@ ADVENTURE_TYPES = [
('other', 'Other') ('other', 'Other')
] ]
TIMEZONES = [
"Africa/Abidjan",
"Africa/Accra",
"Africa/Addis_Ababa",
"Africa/Algiers",
"Africa/Asmera",
"Africa/Bamako",
"Africa/Bangui",
"Africa/Banjul",
"Africa/Bissau",
"Africa/Blantyre",
"Africa/Brazzaville",
"Africa/Bujumbura",
"Africa/Cairo",
"Africa/Casablanca",
"Africa/Ceuta",
"Africa/Conakry",
"Africa/Dakar",
"Africa/Dar_es_Salaam",
"Africa/Djibouti",
"Africa/Douala",
"Africa/El_Aaiun",
"Africa/Freetown",
"Africa/Gaborone",
"Africa/Harare",
"Africa/Johannesburg",
"Africa/Juba",
"Africa/Kampala",
"Africa/Khartoum",
"Africa/Kigali",
"Africa/Kinshasa",
"Africa/Lagos",
"Africa/Libreville",
"Africa/Lome",
"Africa/Luanda",
"Africa/Lubumbashi",
"Africa/Lusaka",
"Africa/Malabo",
"Africa/Maputo",
"Africa/Maseru",
"Africa/Mbabane",
"Africa/Mogadishu",
"Africa/Monrovia",
"Africa/Nairobi",
"Africa/Ndjamena",
"Africa/Niamey",
"Africa/Nouakchott",
"Africa/Ouagadougou",
"Africa/Porto-Novo",
"Africa/Sao_Tome",
"Africa/Tripoli",
"Africa/Tunis",
"Africa/Windhoek",
"America/Adak",
"America/Anchorage",
"America/Anguilla",
"America/Antigua",
"America/Araguaina",
"America/Argentina/La_Rioja",
"America/Argentina/Rio_Gallegos",
"America/Argentina/Salta",
"America/Argentina/San_Juan",
"America/Argentina/San_Luis",
"America/Argentina/Tucuman",
"America/Argentina/Ushuaia",
"America/Aruba",
"America/Asuncion",
"America/Bahia",
"America/Bahia_Banderas",
"America/Barbados",
"America/Belem",
"America/Belize",
"America/Blanc-Sablon",
"America/Boa_Vista",
"America/Bogota",
"America/Boise",
"America/Buenos_Aires",
"America/Cambridge_Bay",
"America/Campo_Grande",
"America/Cancun",
"America/Caracas",
"America/Catamarca",
"America/Cayenne",
"America/Cayman",
"America/Chicago",
"America/Chihuahua",
"America/Ciudad_Juarez",
"America/Coral_Harbour",
"America/Cordoba",
"America/Costa_Rica",
"America/Creston",
"America/Cuiaba",
"America/Curacao",
"America/Danmarkshavn",
"America/Dawson",
"America/Dawson_Creek",
"America/Denver",
"America/Detroit",
"America/Dominica",
"America/Edmonton",
"America/Eirunepe",
"America/El_Salvador",
"America/Fort_Nelson",
"America/Fortaleza",
"America/Glace_Bay",
"America/Godthab",
"America/Goose_Bay",
"America/Grand_Turk",
"America/Grenada",
"America/Guadeloupe",
"America/Guatemala",
"America/Guayaquil",
"America/Guyana",
"America/Halifax",
"America/Havana",
"America/Hermosillo",
"America/Indiana/Knox",
"America/Indiana/Marengo",
"America/Indiana/Petersburg",
"America/Indiana/Tell_City",
"America/Indiana/Vevay",
"America/Indiana/Vincennes",
"America/Indiana/Winamac",
"America/Indianapolis",
"America/Inuvik",
"America/Iqaluit",
"America/Jamaica",
"America/Jujuy",
"America/Juneau",
"America/Kentucky/Monticello",
"America/Kralendijk",
"America/La_Paz",
"America/Lima",
"America/Los_Angeles",
"America/Louisville",
"America/Lower_Princes",
"America/Maceio",
"America/Managua",
"America/Manaus",
"America/Marigot",
"America/Martinique",
"America/Matamoros",
"America/Mazatlan",
"America/Mendoza",
"America/Menominee",
"America/Merida",
"America/Metlakatla",
"America/Mexico_City",
"America/Miquelon",
"America/Moncton",
"America/Monterrey",
"America/Montevideo",
"America/Montserrat",
"America/Nassau",
"America/New_York",
"America/Nome",
"America/Noronha",
"America/North_Dakota/Beulah",
"America/North_Dakota/Center",
"America/North_Dakota/New_Salem",
"America/Ojinaga",
"America/Panama",
"America/Paramaribo",
"America/Phoenix",
"America/Port-au-Prince",
"America/Port_of_Spain",
"America/Porto_Velho",
"America/Puerto_Rico",
"America/Punta_Arenas",
"America/Rankin_Inlet",
"America/Recife",
"America/Regina",
"America/Resolute",
"America/Rio_Branco",
"America/Santarem",
"America/Santiago",
"America/Santo_Domingo",
"America/Sao_Paulo",
"America/Scoresbysund",
"America/Sitka",
"America/St_Barthelemy",
"America/St_Johns",
"America/St_Kitts",
"America/St_Lucia",
"America/St_Thomas",
"America/St_Vincent",
"America/Swift_Current",
"America/Tegucigalpa",
"America/Thule",
"America/Tijuana",
"America/Toronto",
"America/Tortola",
"America/Vancouver",
"America/Whitehorse",
"America/Winnipeg",
"America/Yakutat",
"Antarctica/Casey",
"Antarctica/Davis",
"Antarctica/DumontDUrville",
"Antarctica/Macquarie",
"Antarctica/Mawson",
"Antarctica/McMurdo",
"Antarctica/Palmer",
"Antarctica/Rothera",
"Antarctica/Syowa",
"Antarctica/Troll",
"Antarctica/Vostok",
"Arctic/Longyearbyen",
"Asia/Aden",
"Asia/Almaty",
"Asia/Amman",
"Asia/Anadyr",
"Asia/Aqtau",
"Asia/Aqtobe",
"Asia/Ashgabat",
"Asia/Atyrau",
"Asia/Baghdad",
"Asia/Bahrain",
"Asia/Baku",
"Asia/Bangkok",
"Asia/Barnaul",
"Asia/Beirut",
"Asia/Bishkek",
"Asia/Brunei",
"Asia/Calcutta",
"Asia/Chita",
"Asia/Colombo",
"Asia/Damascus",
"Asia/Dhaka",
"Asia/Dili",
"Asia/Dubai",
"Asia/Dushanbe",
"Asia/Famagusta",
"Asia/Gaza",
"Asia/Hebron",
"Asia/Hong_Kong",
"Asia/Hovd",
"Asia/Irkutsk",
"Asia/Jakarta",
"Asia/Jayapura",
"Asia/Jerusalem",
"Asia/Kabul",
"Asia/Kamchatka",
"Asia/Karachi",
"Asia/Katmandu",
"Asia/Khandyga",
"Asia/Krasnoyarsk",
"Asia/Kuala_Lumpur",
"Asia/Kuching",
"Asia/Kuwait",
"Asia/Macau",
"Asia/Magadan",
"Asia/Makassar",
"Asia/Manila",
"Asia/Muscat",
"Asia/Nicosia",
"Asia/Novokuznetsk",
"Asia/Novosibirsk",
"Asia/Omsk",
"Asia/Oral",
"Asia/Phnom_Penh",
"Asia/Pontianak",
"Asia/Pyongyang",
"Asia/Qatar",
"Asia/Qostanay",
"Asia/Qyzylorda",
"Asia/Rangoon",
"Asia/Riyadh",
"Asia/Saigon",
"Asia/Sakhalin",
"Asia/Samarkand",
"Asia/Seoul",
"Asia/Shanghai",
"Asia/Singapore",
"Asia/Srednekolymsk",
"Asia/Taipei",
"Asia/Tashkent",
"Asia/Tbilisi",
"Asia/Tehran",
"Asia/Thimphu",
"Asia/Tokyo",
"Asia/Tomsk",
"Asia/Ulaanbaatar",
"Asia/Urumqi",
"Asia/Ust-Nera",
"Asia/Vientiane",
"Asia/Vladivostok",
"Asia/Yakutsk",
"Asia/Yekaterinburg",
"Asia/Yerevan",
"Atlantic/Azores",
"Atlantic/Bermuda",
"Atlantic/Canary",
"Atlantic/Cape_Verde",
"Atlantic/Faeroe",
"Atlantic/Madeira",
"Atlantic/Reykjavik",
"Atlantic/South_Georgia",
"Atlantic/St_Helena",
"Atlantic/Stanley",
"Australia/Adelaide",
"Australia/Brisbane",
"Australia/Broken_Hill",
"Australia/Darwin",
"Australia/Eucla",
"Australia/Hobart",
"Australia/Lindeman",
"Australia/Lord_Howe",
"Australia/Melbourne",
"Australia/Perth",
"Australia/Sydney",
"Europe/Amsterdam",
"Europe/Andorra",
"Europe/Astrakhan",
"Europe/Athens",
"Europe/Belgrade",
"Europe/Berlin",
"Europe/Bratislava",
"Europe/Brussels",
"Europe/Bucharest",
"Europe/Budapest",
"Europe/Busingen",
"Europe/Chisinau",
"Europe/Copenhagen",
"Europe/Dublin",
"Europe/Gibraltar",
"Europe/Guernsey",
"Europe/Helsinki",
"Europe/Isle_of_Man",
"Europe/Istanbul",
"Europe/Jersey",
"Europe/Kaliningrad",
"Europe/Kiev",
"Europe/Kirov",
"Europe/Lisbon",
"Europe/Ljubljana",
"Europe/London",
"Europe/Luxembourg",
"Europe/Madrid",
"Europe/Malta",
"Europe/Mariehamn",
"Europe/Minsk",
"Europe/Monaco",
"Europe/Moscow",
"Europe/Oslo",
"Europe/Paris",
"Europe/Podgorica",
"Europe/Prague",
"Europe/Riga",
"Europe/Rome",
"Europe/Samara",
"Europe/San_Marino",
"Europe/Sarajevo",
"Europe/Saratov",
"Europe/Simferopol",
"Europe/Skopje",
"Europe/Sofia",
"Europe/Stockholm",
"Europe/Tallinn",
"Europe/Tirane",
"Europe/Ulyanovsk",
"Europe/Vaduz",
"Europe/Vatican",
"Europe/Vienna",
"Europe/Vilnius",
"Europe/Volgograd",
"Europe/Warsaw",
"Europe/Zagreb",
"Europe/Zurich",
"Indian/Antananarivo",
"Indian/Chagos",
"Indian/Christmas",
"Indian/Cocos",
"Indian/Comoro",
"Indian/Kerguelen",
"Indian/Mahe",
"Indian/Maldives",
"Indian/Mauritius",
"Indian/Mayotte",
"Indian/Reunion",
"Pacific/Apia",
"Pacific/Auckland",
"Pacific/Bougainville",
"Pacific/Chatham",
"Pacific/Easter",
"Pacific/Efate",
"Pacific/Enderbury",
"Pacific/Fakaofo",
"Pacific/Fiji",
"Pacific/Funafuti",
"Pacific/Galapagos",
"Pacific/Gambier",
"Pacific/Guadalcanal",
"Pacific/Guam",
"Pacific/Honolulu",
"Pacific/Kiritimati",
"Pacific/Kosrae",
"Pacific/Kwajalein",
"Pacific/Majuro",
"Pacific/Marquesas",
"Pacific/Midway",
"Pacific/Nauru",
"Pacific/Niue",
"Pacific/Norfolk",
"Pacific/Noumea",
"Pacific/Pago_Pago",
"Pacific/Palau",
"Pacific/Pitcairn",
"Pacific/Ponape",
"Pacific/Port_Moresby",
"Pacific/Rarotonga",
"Pacific/Saipan",
"Pacific/Tahiti",
"Pacific/Tarawa",
"Pacific/Tongatapu",
"Pacific/Truk",
"Pacific/Wake",
"Pacific/Wallis"
]
LODGING_TYPES = [ LODGING_TYPES = [
('hotel', 'Hotel'), ('hotel', 'Hotel'),
('hostel', 'Hostel'), ('hostel', 'Hostel'),
@ -533,13 +112,13 @@ TRANSPORTATION_TYPES = [
] ]
# Assuming you have a default user ID you want to use # Assuming you have a default user ID you want to use
default_user_id = 1 # Replace with an actual user ID default_user = 1 # Replace with an actual user ID
User = get_user_model() User = get_user_model()
class Visit(models.Model): class Visit(models.Model):
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True) id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
adventure = models.ForeignKey('Adventure', on_delete=models.CASCADE, related_name='visits') location = models.ForeignKey('Location', on_delete=models.CASCADE, related_name='visits')
start_date = models.DateTimeField(null=True, blank=True) start_date = models.DateTimeField(null=True, blank=True)
end_date = models.DateTimeField(null=True, blank=True) end_date = models.DateTimeField(null=True, blank=True)
timezone = models.CharField(max_length=50, choices=[(tz, tz) for tz in TIMEZONES], null=True, blank=True) timezone = models.CharField(max_length=50, choices=[(tz, tz) for tz in TIMEZONES], null=True, blank=True)
@ -552,18 +131,18 @@ class Visit(models.Model):
raise ValidationError('The start date must be before or equal to the end date.') raise ValidationError('The start date must be before or equal to the end date.')
def __str__(self): def __str__(self):
return f"{self.adventure.name} - {self.start_date} to {self.end_date}" return f"{self.location.name} - {self.start_date} to {self.end_date}"
class Adventure(models.Model): class Location(models.Model):
#id = models.AutoField(primary_key=True) #id = models.AutoField(primary_key=True)
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True) id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
user_id = models.ForeignKey( user = models.ForeignKey(
User, on_delete=models.CASCADE, default=default_user_id) User, on_delete=models.CASCADE, default=default_user)
category = models.ForeignKey('Category', on_delete=models.SET_NULL, blank=True, null=True) category = models.ForeignKey('Category', on_delete=models.SET_NULL, blank=True, null=True)
name = models.CharField(max_length=200) name = models.CharField(max_length=200)
location = models.CharField(max_length=200, blank=True, null=True) location = models.CharField(max_length=200, blank=True, null=True)
activity_types = ArrayField(models.CharField( tags = ArrayField(models.CharField(
max_length=100), blank=True, null=True) max_length=100), blank=True, null=True)
description = models.TextField(blank=True, null=True) description = models.TextField(blank=True, null=True)
rating = models.FloatField(blank=True, null=True) rating = models.FloatField(blank=True, null=True)
@ -578,12 +157,12 @@ class Adventure(models.Model):
country = models.ForeignKey(Country, on_delete=models.SET_NULL, blank=True, null=True) country = models.ForeignKey(Country, on_delete=models.SET_NULL, blank=True, null=True)
# Changed from ForeignKey to ManyToManyField # Changed from ForeignKey to ManyToManyField
collections = models.ManyToManyField('Collection', blank=True, related_name='adventures') collections = models.ManyToManyField('Collection', blank=True, related_name='locations')
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True) updated_at = models.DateTimeField(auto_now=True)
objects = AdventureManager() objects = LocationManager()
def is_visited_status(self): def is_visited_status(self):
current_date = timezone.now().date() current_date = timezone.now().date()
@ -609,18 +188,18 @@ class Adventure(models.Model):
if self.pk: # Only check if the instance has been saved if self.pk: # Only check if the instance has been saved
for collection in self.collections.all(): for collection in self.collections.all():
if collection.is_public and not self.is_public: if collection.is_public and not self.is_public:
raise ValidationError(f'Adventures associated with a public collection must be public. Collection: {collection.name} Adventure: {self.name}') raise ValidationError(f'Locations associated with a public collection must be public. Collection: {collection.name} Location: {self.name}')
# Only enforce same-user constraint for non-shared collections # Only enforce same-user constraint for non-shared collections
if self.user_id != collection.user_id: if self.user != collection.user:
# Check if this is a shared collection scenario # Check if this is a shared collection scenario
# Allow if the adventure owner has access to the collection through sharing # Allow if the location owner has access to the collection through sharing
if not collection.shared_with.filter(uuid=self.user_id.uuid).exists(): if not collection.shared_with.filter(uuid=self.user.uuid).exists():
raise ValidationError(f'Adventures must be associated with collections owned by the same user or shared collections. Collection owner: {collection.user_id.username} Adventure owner: {self.user_id.username}') raise ValidationError(f'Locations must be associated with collections owned by the same user or shared collections. Collection owner: {collection.user.username} Location owner: {self.user.username}')
if self.category: if self.category:
if self.user_id != self.category.user_id: if self.user != self.category.user:
raise ValidationError(f'Adventures must be associated with categories owned by the same user. Category owner: {self.category.user_id.username} Adventure owner: {self.user_id.username}') raise ValidationError(f'Locations must be associated with categories owned by the same user. Category owner: {self.category.user.username} Location owner: {self.user.username}')
def save(self, force_insert=False, force_update=False, using=None, update_fields=None, _skip_geocode=False, _skip_shared_validation=False): def save(self, force_insert=False, force_update=False, using=None, update_fields=None, _skip_geocode=False, _skip_shared_validation=False):
if force_insert and force_update: if force_insert and force_update:
@ -628,7 +207,7 @@ class Adventure(models.Model):
if not self.category: if not self.category:
category, _ = Category.objects.get_or_create( category, _ = Category.objects.get_or_create(
user_id=self.user_id, user=self.user,
name='general', name='general',
defaults={'display_name': 'General', 'icon': '🌍'} defaults={'display_name': 'General', 'icon': '🌍'}
) )
@ -671,8 +250,8 @@ class Adventure(models.Model):
class Collection(models.Model): class Collection(models.Model):
#id = models.AutoField(primary_key=True) #id = models.AutoField(primary_key=True)
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True) id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
user_id = models.ForeignKey( user = models.ForeignKey(
User, on_delete=models.CASCADE, default=default_user_id) User, on_delete=models.CASCADE, default=default_user)
name = models.CharField(max_length=200) name = models.CharField(max_length=200)
description = models.TextField(blank=True, null=True) description = models.TextField(blank=True, null=True)
is_public = models.BooleanField(default=False) is_public = models.BooleanField(default=False)
@ -684,13 +263,13 @@ class Collection(models.Model):
shared_with = models.ManyToManyField(User, related_name='shared_with', blank=True) shared_with = models.ManyToManyField(User, related_name='shared_with', blank=True)
link = models.URLField(blank=True, null=True, max_length=2083) link = models.URLField(blank=True, null=True, max_length=2083)
# if connected adventures are private and collection is public, raise an error # if connected locations are private and collection is public, raise an error
def clean(self): def clean(self):
if self.is_public and self.pk: # Only check if the instance has a primary key if self.is_public and self.pk: # Only check if the instance has a primary key
# Updated to use the new related_name 'adventures' # Updated to use the new related_name 'locations'
for adventure in self.adventures.all(): for location in self.locations.all():
if not adventure.is_public: if not location.is_public:
raise ValidationError(f'Public collections cannot be associated with private adventures. Collection: {self.name} Adventure: {adventure.name}') raise ValidationError(f'Public collections cannot be associated with private locations. Collection: {self.name} Location: {location.name}')
def __str__(self): def __str__(self):
return self.name return self.name
@ -698,8 +277,8 @@ class Collection(models.Model):
class Transportation(models.Model): class Transportation(models.Model):
#id = models.AutoField(primary_key=True) #id = models.AutoField(primary_key=True)
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True) id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
user_id = models.ForeignKey( user = models.ForeignKey(
User, on_delete=models.CASCADE, default=default_user_id) User, on_delete=models.CASCADE, default=default_user)
type = models.CharField(max_length=100, choices=TRANSPORTATION_TYPES) type = models.CharField(max_length=100, choices=TRANSPORTATION_TYPES)
name = models.CharField(max_length=200) name = models.CharField(max_length=200)
description = models.TextField(blank=True, null=True) description = models.TextField(blank=True, null=True)
@ -729,8 +308,8 @@ class Transportation(models.Model):
if self.collection: if self.collection:
if self.collection.is_public and not self.is_public: if self.collection.is_public and not self.is_public:
raise ValidationError('Transportations associated with a public collection must be public. Collection: ' + self.collection.name + ' Transportation: ' + self.name) raise ValidationError('Transportations associated with a public collection must be public. Collection: ' + self.collection.name + ' Transportation: ' + self.name)
if self.user_id != self.collection.user_id: if self.user != self.collection.user:
raise ValidationError('Transportations must be associated with collections owned by the same user. Collection owner: ' + self.collection.user_id.username + ' Transportation owner: ' + self.user_id.username) raise ValidationError('Transportations must be associated with collections owned by the same user. Collection owner: ' + self.collection.user.username + ' Transportation owner: ' + self.user.username)
def __str__(self): def __str__(self):
return self.name return self.name
@ -738,8 +317,8 @@ class Transportation(models.Model):
class Note(models.Model): class Note(models.Model):
#id = models.AutoField(primary_key=True) #id = models.AutoField(primary_key=True)
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True) id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
user_id = models.ForeignKey( user = models.ForeignKey(
User, on_delete=models.CASCADE, default=default_user_id) User, on_delete=models.CASCADE, default=default_user)
name = models.CharField(max_length=200) name = models.CharField(max_length=200)
content = models.TextField(blank=True, null=True) content = models.TextField(blank=True, null=True)
links = ArrayField(models.URLField(), blank=True, null=True) links = ArrayField(models.URLField(), blank=True, null=True)
@ -753,8 +332,8 @@ class Note(models.Model):
if self.collection: if self.collection:
if self.collection.is_public and not self.is_public: if self.collection.is_public and not self.is_public:
raise ValidationError('Notes associated with a public collection must be public. Collection: ' + self.collection.name + ' Transportation: ' + self.name) raise ValidationError('Notes associated with a public collection must be public. Collection: ' + self.collection.name + ' Transportation: ' + self.name)
if self.user_id != self.collection.user_id: if self.user != self.collection.user:
raise ValidationError('Notes must be associated with collections owned by the same user. Collection owner: ' + self.collection.user_id.username + ' Transportation owner: ' + self.user_id.username) raise ValidationError('Notes must be associated with collections owned by the same user. Collection owner: ' + self.collection.user.username + ' Transportation owner: ' + self.user.username)
def __str__(self): def __str__(self):
return self.name return self.name
@ -762,8 +341,8 @@ class Note(models.Model):
class Checklist(models.Model): class Checklist(models.Model):
# id = models.AutoField(primary_key=True) # id = models.AutoField(primary_key=True)
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True) id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
user_id = models.ForeignKey( user = models.ForeignKey(
User, on_delete=models.CASCADE, default=default_user_id) User, on_delete=models.CASCADE, default=default_user)
name = models.CharField(max_length=200) name = models.CharField(max_length=200)
date = models.DateField(blank=True, null=True) date = models.DateField(blank=True, null=True)
is_public = models.BooleanField(default=False) is_public = models.BooleanField(default=False)
@ -775,8 +354,8 @@ class Checklist(models.Model):
if self.collection: if self.collection:
if self.collection.is_public and not self.is_public: if self.collection.is_public and not self.is_public:
raise ValidationError('Checklists associated with a public collection must be public. Collection: ' + self.collection.name + ' Checklist: ' + self.name) raise ValidationError('Checklists associated with a public collection must be public. Collection: ' + self.collection.name + ' Checklist: ' + self.name)
if self.user_id != self.collection.user_id: if self.user != self.collection.user:
raise ValidationError('Checklists must be associated with collections owned by the same user. Collection owner: ' + self.collection.user_id.username + ' Checklist owner: ' + self.user_id.username) raise ValidationError('Checklists must be associated with collections owned by the same user. Collection owner: ' + self.collection.user.username + ' Checklist owner: ' + self.user.username)
def __str__(self): def __str__(self):
return self.name return self.name
@ -784,8 +363,8 @@ class Checklist(models.Model):
class ChecklistItem(models.Model): class ChecklistItem(models.Model):
#id = models.AutoField(primary_key=True) #id = models.AutoField(primary_key=True)
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True) id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
user_id = models.ForeignKey( user = models.ForeignKey(
User, on_delete=models.CASCADE, default=default_user_id) User, on_delete=models.CASCADE, default=default_user)
name = models.CharField(max_length=200) name = models.CharField(max_length=200)
is_checked = models.BooleanField(default=False) is_checked = models.BooleanField(default=False)
checklist = models.ForeignKey('Checklist', on_delete=models.CASCADE) checklist = models.ForeignKey('Checklist', on_delete=models.CASCADE)
@ -795,8 +374,8 @@ class ChecklistItem(models.Model):
def clean(self): def clean(self):
if self.checklist.is_public and not self.checklist.is_public: if self.checklist.is_public and not self.checklist.is_public:
raise ValidationError('Checklist items associated with a public checklist must be public. Checklist: ' + self.checklist.name + ' Checklist item: ' + self.name) raise ValidationError('Checklist items associated with a public checklist must be public. Checklist: ' + self.checklist.name + ' Checklist item: ' + self.name)
if self.user_id != self.checklist.user_id: if self.user != self.checklist.user:
raise ValidationError('Checklist items must be associated with checklists owned by the same user. Checklist owner: ' + self.checklist.user_id.username + ' Checklist item owner: ' + self.user_id.username) raise ValidationError('Checklist items must be associated with checklists owned by the same user. Checklist owner: ' + self.checklist.user.username + ' Checklist item owner: ' + self.user.username)
def __str__(self): def __str__(self):
return self.name return self.name
@ -812,9 +391,9 @@ class PathAndRename:
filename = f"{uuid.uuid4()}.{ext}" filename = f"{uuid.uuid4()}.{ext}"
return os.path.join(self.path, filename) return os.path.join(self.path, filename)
class AdventureImage(models.Model): class LocationImage(models.Model):
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True) id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
user_id = models.ForeignKey(User, on_delete=models.CASCADE, default=default_user_id) user = models.ForeignKey(User, on_delete=models.CASCADE, default=default_user)
image = ResizedImageField( image = ResizedImageField(
force_format="WEBP", force_format="WEBP",
quality=75, quality=75,
@ -823,7 +402,7 @@ class AdventureImage(models.Model):
null=True, null=True,
) )
immich_id = models.CharField(max_length=200, null=True, blank=True) immich_id = models.CharField(max_length=200, null=True, blank=True)
adventure = models.ForeignKey(Adventure, related_name='images', on_delete=models.CASCADE) location = models.ForeignKey(Location, related_name='images', on_delete=models.CASCADE)
is_primary = models.BooleanField(default=False) is_primary = models.BooleanField(default=False)
def clean(self): def clean(self):
@ -858,10 +437,10 @@ class AdventureImage(models.Model):
class Attachment(models.Model): class Attachment(models.Model):
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True) id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
user_id = models.ForeignKey( user = models.ForeignKey(
User, on_delete=models.CASCADE, default=default_user_id) User, on_delete=models.CASCADE, default=default_user)
file = models.FileField(upload_to=PathAndRename('attachments/'),validators=[validate_file_extension]) file = models.FileField(upload_to=PathAndRename('attachments/'),validators=[validate_file_extension])
adventure = models.ForeignKey(Adventure, related_name='attachments', on_delete=models.CASCADE) location = models.ForeignKey(Location, related_name='attachments', on_delete=models.CASCADE)
name = models.CharField(max_length=200, null=True, blank=True) name = models.CharField(max_length=200, null=True, blank=True)
def delete(self, *args, **kwargs): def delete(self, *args, **kwargs):
@ -874,15 +453,15 @@ class Attachment(models.Model):
class Category(models.Model): class Category(models.Model):
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True) id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
user_id = models.ForeignKey( user = models.ForeignKey(
User, on_delete=models.CASCADE, default=default_user_id) User, on_delete=models.CASCADE, default=default_user)
name = models.CharField(max_length=200) name = models.CharField(max_length=200)
display_name = models.CharField(max_length=200) display_name = models.CharField(max_length=200)
icon = models.CharField(max_length=200, default='🌍') icon = models.CharField(max_length=200, default='🌍')
class Meta: class Meta:
verbose_name_plural = 'Categories' verbose_name_plural = 'Categories'
unique_together = ['name', 'user_id'] unique_together = ['name', 'user']
def clean(self) -> None: def clean(self) -> None:
self.name = self.name.lower().strip() self.name = self.name.lower().strip()
@ -895,8 +474,8 @@ class Category(models.Model):
class Lodging(models.Model): class Lodging(models.Model):
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True) id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True)
user_id = models.ForeignKey( user = models.ForeignKey(
User, on_delete=models.CASCADE, default=default_user_id) User, on_delete=models.CASCADE, default=default_user)
name = models.CharField(max_length=200) name = models.CharField(max_length=200)
type = models.CharField(max_length=100, choices=LODGING_TYPES, default='other') type = models.CharField(max_length=100, choices=LODGING_TYPES, default='other')
description = models.TextField(blank=True, null=True) description = models.TextField(blank=True, null=True)
@ -921,9 +500,9 @@ class Lodging(models.Model):
if self.collection: if self.collection:
if self.collection.is_public and not self.is_public: if self.collection.is_public and not self.is_public:
raise ValidationError('Lodging associated with a public collection must be public. Collection: ' + self.collection.name + ' Loging: ' + self.name) raise ValidationError('Lodging associated with a public collection must be public. Collection: ' + self.collection.name + ' Lodging: ' + self.name)
if self.user_id != self.collection.user_id: if self.user != self.collection.user:
raise ValidationError('Lodging must be associated with collections owned by the same user. Collection owner: ' + self.collection.user_id.username + ' Lodging owner: ' + self.user_id.username) raise ValidationError('Lodging must be associated with collections owned by the same user. Collection owner: ' + self.collection.user.username + ' Lodging owner: ' + self.user.username)
def __str__(self): def __str__(self):
return self.name return self.name

View file

@ -7,8 +7,8 @@ class IsOwnerOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj): def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS: if request.method in permissions.SAFE_METHODS:
return True return True
# obj.user_id is FK to User, compare with request.user # obj.user is FK to User, compare with request.user
return obj.user_id == request.user return obj.user == request.user
class IsPublicReadOnly(permissions.BasePermission): class IsPublicReadOnly(permissions.BasePermission):
@ -17,8 +17,8 @@ class IsPublicReadOnly(permissions.BasePermission):
""" """
def has_object_permission(self, request, view, obj): def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS: if request.method in permissions.SAFE_METHODS:
return obj.is_public or obj.user_id == request.user return obj.is_public or obj.user == request.user
return obj.user_id == request.user return obj.user == request.user
class CollectionShared(permissions.BasePermission): class CollectionShared(permissions.BasePermission):
@ -39,7 +39,7 @@ class CollectionShared(permissions.BasePermission):
if obj.shared_with.filter(id=user.id).exists(): if obj.shared_with.filter(id=user.id).exists():
return True return True
# If obj is an Adventure (has collections M2M) # If obj is a Location (has collections M2M)
if hasattr(obj, 'collections'): if hasattr(obj, 'collections'):
# Check if user is in shared_with of any related collection # Check if user is in shared_with of any related collection
shared_collections = obj.collections.filter(shared_with=user) shared_collections = obj.collections.filter(shared_with=user)
@ -48,10 +48,10 @@ class CollectionShared(permissions.BasePermission):
# Read permission if public or owner # Read permission if public or owner
if request.method in permissions.SAFE_METHODS: if request.method in permissions.SAFE_METHODS:
return obj.is_public or obj.user_id == user return obj.is_public or obj.user == user
# Write permission only if owner or shared user via collections # Write permission only if owner or shared user via collections
if obj.user_id == user: if obj.user == user:
return True return True
if hasattr(obj, 'collections'): if hasattr(obj, 'collections'):
@ -76,7 +76,7 @@ class IsOwnerOrSharedWithFullAccess(permissions.BasePermission):
if request.method in permissions.SAFE_METHODS: if request.method in permissions.SAFE_METHODS:
if obj.is_public: if obj.is_public:
return True return True
if obj.user_id == user: if obj.user == user:
return True return True
# If user in shared_with of any collection related to obj # If user in shared_with of any collection related to obj
if hasattr(obj, 'collections') and obj.collections.filter(shared_with=user).exists(): if hasattr(obj, 'collections') and obj.collections.filter(shared_with=user).exists():
@ -88,7 +88,7 @@ class IsOwnerOrSharedWithFullAccess(permissions.BasePermission):
return False return False
# For write methods, allow if owner or shared user # For write methods, allow if owner or shared user
if obj.user_id == user: if obj.user == user:
return True return True
if hasattr(obj, 'collections') and obj.collections.filter(shared_with=user).exists(): if hasattr(obj, 'collections') and obj.collections.filter(shared_with=user).exists():
return True return True

View file

@ -1,6 +1,6 @@
from django.utils import timezone from django.utils import timezone
import os import os
from .models import Adventure, AdventureImage, ChecklistItem, Collection, Note, Transportation, Checklist, Visit, Category, Attachment, Lodging from .models import Location, LocationImage, ChecklistItem, Collection, Note, Transportation, Checklist, Visit, Category, Attachment, Lodging
from rest_framework import serializers from rest_framework import serializers
from main.utils import CustomModelSerializer from main.utils import CustomModelSerializer
from users.serializers import CustomUserDetailsSerializer from users.serializers import CustomUserDetailsSerializer
@ -9,17 +9,17 @@ from geopy.distance import geodesic
from integrations.models import ImmichIntegration from integrations.models import ImmichIntegration
class AdventureImageSerializer(CustomModelSerializer): class LocationImageSerializer(CustomModelSerializer):
class Meta: class Meta:
model = AdventureImage model = LocationImage
fields = ['id', 'image', 'adventure', 'is_primary', 'user_id', 'immich_id'] fields = ['id', 'image', 'location', 'is_primary', 'user', 'immich_id']
read_only_fields = ['id', 'user_id'] read_only_fields = ['id', 'user']
def to_representation(self, instance): def to_representation(self, instance):
# If immich_id is set, check for user integration once # If immich_id is set, check for user integration once
integration = None integration = None
if instance.immich_id: if instance.immich_id:
integration = ImmichIntegration.objects.filter(user=instance.user_id).first() integration = ImmichIntegration.objects.filter(user=instance.user).first()
if not integration: if not integration:
return None # Skip if Immich image but no integration return None # Skip if Immich image but no integration
@ -42,8 +42,8 @@ class AttachmentSerializer(CustomModelSerializer):
extension = serializers.SerializerMethodField() extension = serializers.SerializerMethodField()
class Meta: class Meta:
model = Attachment model = Attachment
fields = ['id', 'file', 'adventure', 'extension', 'name', 'user_id'] fields = ['id', 'file', 'location', 'extension', 'name', 'user']
read_only_fields = ['id', 'user_id'] read_only_fields = ['id', 'user']
def get_extension(self, obj): def get_extension(self, obj):
return obj.file.name.split('.')[-1] return obj.file.name.split('.')[-1]
@ -59,11 +59,11 @@ class AttachmentSerializer(CustomModelSerializer):
return representation return representation
class CategorySerializer(serializers.ModelSerializer): class CategorySerializer(serializers.ModelSerializer):
num_adventures = serializers.SerializerMethodField() num_locations = serializers.SerializerMethodField()
class Meta: class Meta:
model = Category model = Category
fields = ['id', 'name', 'display_name', 'icon', 'num_adventures'] fields = ['id', 'name', 'display_name', 'icon', 'num_locations']
read_only_fields = ['id', 'num_adventures'] read_only_fields = ['id', 'num_locations']
def validate_name(self, value): def validate_name(self, value):
return value.lower() return value.lower()
@ -71,7 +71,7 @@ class CategorySerializer(serializers.ModelSerializer):
def create(self, validated_data): def create(self, validated_data):
user = self.context['request'].user user = self.context['request'].user
validated_data['name'] = validated_data['name'].lower() validated_data['name'] = validated_data['name'].lower()
return Category.objects.create(user_id=user, **validated_data) return Category.objects.create(user=user, **validated_data)
def update(self, instance, validated_data): def update(self, instance, validated_data):
for attr, value in validated_data.items(): for attr, value in validated_data.items():
@ -81,8 +81,8 @@ class CategorySerializer(serializers.ModelSerializer):
instance.save() instance.save()
return instance return instance
def get_num_adventures(self, obj): def get_num_locations(self, obj):
return Adventure.objects.filter(category=obj, user_id=obj.user_id).count() return Location.objects.filter(category=obj, user=obj.user).count()
class VisitSerializer(serializers.ModelSerializer): class VisitSerializer(serializers.ModelSerializer):
@ -91,13 +91,12 @@ class VisitSerializer(serializers.ModelSerializer):
fields = ['id', 'start_date', 'end_date', 'timezone', 'notes'] fields = ['id', 'start_date', 'end_date', 'timezone', 'notes']
read_only_fields = ['id'] read_only_fields = ['id']
class AdventureSerializer(CustomModelSerializer): class LocationSerializer(CustomModelSerializer):
images = serializers.SerializerMethodField() images = serializers.SerializerMethodField()
visits = VisitSerializer(many=True, read_only=False, required=False) visits = VisitSerializer(many=True, read_only=False, required=False)
attachments = AttachmentSerializer(many=True, read_only=True) attachments = AttachmentSerializer(many=True, read_only=True)
category = CategorySerializer(read_only=False, required=False) category = CategorySerializer(read_only=False, required=False)
is_visited = serializers.SerializerMethodField() is_visited = serializers.SerializerMethodField()
user = serializers.SerializerMethodField()
country = CountrySerializer(read_only=True) country = CountrySerializer(read_only=True)
region = RegionSerializer(read_only=True) region = RegionSerializer(read_only=True)
city = CitySerializer(read_only=True) city = CitySerializer(read_only=True)
@ -108,16 +107,22 @@ class AdventureSerializer(CustomModelSerializer):
) )
class Meta: class Meta:
model = Adventure model = Location
fields = [ fields = [
'id', 'user_id', 'name', 'description', 'rating', 'activity_types', 'location', 'id', 'name', 'description', 'rating', 'tags', 'location',
'is_public', 'collections', 'created_at', 'updated_at', 'images', 'link', 'longitude', 'is_public', 'collections', 'created_at', 'updated_at', 'images', 'link', 'longitude',
'latitude', 'visits', 'is_visited', 'category', 'attachments', 'user', 'city', 'country', 'region' 'latitude', 'visits', 'is_visited', 'category', 'attachments', 'user', 'city', 'country', 'region'
] ]
read_only_fields = ['id', 'created_at', 'updated_at', 'user_id', 'is_visited', 'user'] read_only_fields = ['id', 'created_at', 'updated_at', 'user', 'is_visited']
# Makes it so the whole user object is returned in the serializer instead of just the user uuid
def to_representation(self, instance):
representation = super().to_representation(instance)
representation['user'] = CustomUserDetailsSerializer(instance.user, context=self.context).data
return representation
def get_images(self, obj): def get_images(self, obj):
serializer = AdventureImageSerializer(obj.images.all(), many=True, context=self.context) serializer = LocationImageSerializer(obj.images.all(), many=True, context=self.context)
# Filter out None values from the serialized data # Filter out None values from the serialized data
return [image for image in serializer.data if image is not None] return [image for image in serializer.data if image is not None]
@ -128,7 +133,7 @@ class AdventureSerializer(CustomModelSerializer):
user = self.context['request'].user user = self.context['request'].user
for collection in collections: for collection in collections:
if collection.user_id != user: if collection.user != user:
raise serializers.ValidationError( raise serializers.ValidationError(
f"Collection '{collection.name}' does not belong to the current user." f"Collection '{collection.name}' does not belong to the current user."
) )
@ -140,7 +145,7 @@ class AdventureSerializer(CustomModelSerializer):
if category_data: if category_data:
user = self.context['request'].user user = self.context['request'].user
name = category_data.get('name', '').lower() name = category_data.get('name', '').lower()
existing_category = Category.objects.filter(user_id=user, name=name).first() existing_category = Category.objects.filter(user=user, name=name).first()
if existing_category: if existing_category:
return existing_category return existing_category
category_data['name'] = name category_data['name'] = name
@ -162,7 +167,7 @@ class AdventureSerializer(CustomModelSerializer):
icon = category_data.icon icon = category_data.icon
category, created = Category.objects.get_or_create( category, created = Category.objects.get_or_create(
user_id=user, user=user,
name=name, name=name,
defaults={ defaults={
'display_name': display_name, 'display_name': display_name,
@ -171,10 +176,6 @@ class AdventureSerializer(CustomModelSerializer):
) )
return category return category
def get_user(self, obj):
user = obj.user_id
return CustomUserDetailsSerializer(user).data
def get_is_visited(self, obj): def get_is_visited(self, obj):
return obj.is_visited_status() return obj.is_visited_status()
@ -184,24 +185,24 @@ class AdventureSerializer(CustomModelSerializer):
collections_data = validated_data.pop('collections', []) collections_data = validated_data.pop('collections', [])
print(category_data) print(category_data)
adventure = Adventure.objects.create(**validated_data) location = Location.objects.create(**validated_data)
# Handle visits # Handle visits
for visit_data in visits_data: for visit_data in visits_data:
Visit.objects.create(adventure=adventure, **visit_data) Visit.objects.create(location=location, **visit_data)
# Handle category # Handle category
if category_data: if category_data:
category = self.get_or_create_category(category_data) category = self.get_or_create_category(category_data)
adventure.category = category location.category = category
# Handle collections - set after adventure is saved # Handle collections - set after location is saved
if collections_data: if collections_data:
adventure.collections.set(collections_data) location.collections.set(collections_data)
adventure.save() location.save()
return adventure return location
def update(self, instance, validated_data): def update(self, instance, validated_data):
has_visits = 'visits' in validated_data has_visits = 'visits' in validated_data
@ -214,9 +215,9 @@ class AdventureSerializer(CustomModelSerializer):
for attr, value in validated_data.items(): for attr, value in validated_data.items():
setattr(instance, attr, value) setattr(instance, attr, value)
# Handle category - ONLY allow the adventure owner to change categories # Handle category - ONLY allow the location owner to change categories
user = self.context['request'].user user = self.context['request'].user
if category_data and instance.user_id == user: if category_data and instance.user == user:
# Only the owner can set categories # Only the owner can set categories
category = self.get_or_create_category(category_data) category = self.get_or_create_category(category_data)
instance.category = category instance.category = category
@ -241,13 +242,13 @@ class AdventureSerializer(CustomModelSerializer):
visit.save() visit.save()
updated_visit_ids.add(visit_id) updated_visit_ids.add(visit_id)
else: else:
new_visit = Visit.objects.create(adventure=instance, **visit_data) new_visit = Visit.objects.create(location=instance, **visit_data)
updated_visit_ids.add(new_visit.id) updated_visit_ids.add(new_visit.id)
visits_to_delete = current_visit_ids - updated_visit_ids visits_to_delete = current_visit_ids - updated_visit_ids
instance.visits.filter(id__in=visits_to_delete).delete() instance.visits.filter(id__in=visits_to_delete).delete()
# call save on the adventure to update the updated_at field and trigger any geocoding # call save on the location to update the updated_at field and trigger any geocoding
instance.save() instance.save()
return instance return instance
@ -258,13 +259,13 @@ class TransportationSerializer(CustomModelSerializer):
class Meta: class Meta:
model = Transportation model = Transportation
fields = [ fields = [
'id', 'user_id', 'type', 'name', 'description', 'rating', 'id', 'user', 'type', 'name', 'description', 'rating',
'link', 'date', 'flight_number', 'from_location', 'to_location', 'link', 'date', 'flight_number', 'from_location', 'to_location',
'is_public', 'collection', 'created_at', 'updated_at', 'end_date', 'is_public', 'collection', 'created_at', 'updated_at', 'end_date',
'origin_latitude', 'origin_longitude', 'destination_latitude', 'destination_longitude', 'origin_latitude', 'origin_longitude', 'destination_latitude', 'destination_longitude',
'start_timezone', 'end_timezone', 'distance' # ✅ Add distance here 'start_timezone', 'end_timezone', 'distance'
] ]
read_only_fields = ['id', 'created_at', 'updated_at', 'user_id', 'distance'] read_only_fields = ['id', 'created_at', 'updated_at', 'user', 'distance']
def get_distance(self, obj): def get_distance(self, obj):
if ( if (
@ -284,29 +285,29 @@ class LodgingSerializer(CustomModelSerializer):
class Meta: class Meta:
model = Lodging model = Lodging
fields = [ fields = [
'id', 'user_id', 'name', 'description', 'rating', 'link', 'check_in', 'check_out', 'id', 'user', 'name', 'description', 'rating', 'link', 'check_in', 'check_out',
'reservation_number', 'price', 'latitude', 'longitude', 'location', 'is_public', 'reservation_number', 'price', 'latitude', 'longitude', 'location', 'is_public',
'collection', 'created_at', 'updated_at', 'type', 'timezone' 'collection', 'created_at', 'updated_at', 'type', 'timezone'
] ]
read_only_fields = ['id', 'created_at', 'updated_at', 'user_id'] read_only_fields = ['id', 'created_at', 'updated_at', 'user']
class NoteSerializer(CustomModelSerializer): class NoteSerializer(CustomModelSerializer):
class Meta: class Meta:
model = Note model = Note
fields = [ fields = [
'id', 'user_id', 'name', 'content', 'date', 'links', 'id', 'user', 'name', 'content', 'date', 'links',
'is_public', 'collection', 'created_at', 'updated_at' 'is_public', 'collection', 'created_at', 'updated_at'
] ]
read_only_fields = ['id', 'created_at', 'updated_at', 'user_id'] read_only_fields = ['id', 'created_at', 'updated_at', 'user']
class ChecklistItemSerializer(CustomModelSerializer): class ChecklistItemSerializer(CustomModelSerializer):
class Meta: class Meta:
model = ChecklistItem model = ChecklistItem
fields = [ fields = [
'id', 'user_id', 'name', 'is_checked', 'checklist', 'created_at', 'updated_at' 'id', 'user', 'name', 'is_checked', 'checklist', 'created_at', 'updated_at'
] ]
read_only_fields = ['id', 'created_at', 'updated_at', 'user_id', 'checklist'] read_only_fields = ['id', 'created_at', 'updated_at', 'user', 'checklist']
class ChecklistSerializer(CustomModelSerializer): class ChecklistSerializer(CustomModelSerializer):
items = ChecklistItemSerializer(many=True, source='checklistitem_set') items = ChecklistItemSerializer(many=True, source='checklistitem_set')
@ -314,21 +315,21 @@ class ChecklistSerializer(CustomModelSerializer):
class Meta: class Meta:
model = Checklist model = Checklist
fields = [ fields = [
'id', 'user_id', 'name', 'date', 'is_public', 'collection', 'created_at', 'updated_at', 'items' 'id', 'user', 'name', 'date', 'is_public', 'collection', 'created_at', 'updated_at', 'items'
] ]
read_only_fields = ['id', 'created_at', 'updated_at', 'user_id'] read_only_fields = ['id', 'created_at', 'updated_at', 'user']
def create(self, validated_data): def create(self, validated_data):
items_data = validated_data.pop('checklistitem_set') items_data = validated_data.pop('checklistitem_set')
checklist = Checklist.objects.create(**validated_data) checklist = Checklist.objects.create(**validated_data)
for item_data in items_data: for item_data in items_data:
# Remove user_id from item_data to avoid constraint issues # Remove user from item_data to avoid constraint issues
item_data.pop('user_id', None) item_data.pop('user', None)
# Set user_id from the parent checklist # Set user from the parent checklist
ChecklistItem.objects.create( ChecklistItem.objects.create(
checklist=checklist, checklist=checklist,
user_id=checklist.user_id, user=checklist.user,
**item_data **item_data
) )
return checklist return checklist
@ -348,8 +349,8 @@ class ChecklistSerializer(CustomModelSerializer):
# Update or create items # Update or create items
updated_item_ids = set() updated_item_ids = set()
for item_data in items_data: for item_data in items_data:
# Remove user_id from item_data to avoid constraint issues # Remove user from item_data to avoid constraint issues
item_data.pop('user_id', None) item_data.pop('user', None)
item_id = item_data.get('id') item_id = item_data.get('id')
if item_id: if item_id:
@ -363,14 +364,14 @@ class ChecklistSerializer(CustomModelSerializer):
# If ID is provided but doesn't exist, create new item # If ID is provided but doesn't exist, create new item
ChecklistItem.objects.create( ChecklistItem.objects.create(
checklist=instance, checklist=instance,
user_id=instance.user_id, user=instance.user,
**item_data **item_data
) )
else: else:
# If no ID is provided, create new item # If no ID is provided, create new item
ChecklistItem.objects.create( ChecklistItem.objects.create(
checklist=instance, checklist=instance,
user_id=instance.user_id, user=instance.user,
**item_data **item_data
) )
@ -391,7 +392,7 @@ class ChecklistSerializer(CustomModelSerializer):
return data return data
class CollectionSerializer(CustomModelSerializer): class CollectionSerializer(CustomModelSerializer):
adventures = AdventureSerializer(many=True, read_only=True) locations = LocationSerializer(many=True, read_only=True)
transportations = TransportationSerializer(many=True, read_only=True, source='transportation_set') transportations = TransportationSerializer(many=True, read_only=True, source='transportation_set')
notes = NoteSerializer(many=True, read_only=True, source='note_set') notes = NoteSerializer(many=True, read_only=True, source='note_set')
checklists = ChecklistSerializer(many=True, read_only=True, source='checklist_set') checklists = ChecklistSerializer(many=True, read_only=True, source='checklist_set')
@ -399,8 +400,8 @@ class CollectionSerializer(CustomModelSerializer):
class Meta: class Meta:
model = Collection model = Collection
fields = ['id', 'description', 'user_id', 'name', 'is_public', 'adventures', 'created_at', 'start_date', 'end_date', 'transportations', 'notes', 'updated_at', 'checklists', 'is_archived', 'shared_with', 'link', 'lodging'] fields = ['id', 'description', 'user', 'name', 'is_public', 'locations', 'created_at', 'start_date', 'end_date', 'transportations', 'notes', 'updated_at', 'checklists', 'is_archived', 'shared_with', 'link', 'lodging']
read_only_fields = ['id', 'created_at', 'updated_at', 'user_id'] read_only_fields = ['id', 'created_at', 'updated_at', 'user']
def to_representation(self, instance): def to_representation(self, instance):
representation = super().to_representation(instance) representation = super().to_representation(instance)

View file

@ -1,8 +1,8 @@
from django.db.models.signals import m2m_changed from django.db.models.signals import m2m_changed
from django.dispatch import receiver from django.dispatch import receiver
from adventures.models import Adventure from adventures.models import Location
@receiver(m2m_changed, sender=Adventure.collections.through) @receiver(m2m_changed, sender=Location.collections.through)
def update_adventure_publicity(sender, instance, action, **kwargs): def update_adventure_publicity(sender, instance, action, **kwargs):
""" """
Signal handler to update adventure publicity when collections are added/removed Signal handler to update adventure publicity when collections are added/removed

View file

@ -3,11 +3,11 @@ from rest_framework.routers import DefaultRouter
from adventures.views import * from adventures.views import *
router = DefaultRouter() router = DefaultRouter()
router.register(r'adventures', AdventureViewSet, basename='adventures') router.register(r'locations', LocationViewSet, basename='locations')
router.register(r'collections', CollectionViewSet, basename='collections') router.register(r'collections', CollectionViewSet, basename='collections')
router.register(r'stats', StatsViewSet, basename='stats') router.register(r'stats', StatsViewSet, basename='stats')
router.register(r'generate', GenerateDescription, basename='generate') router.register(r'generate', GenerateDescription, basename='generate')
router.register(r'activity-types', ActivityTypesView, basename='activity-types') router.register(r'tags', ActivityTypesView, basename='tags')
router.register(r'transportations', TransportationViewSet, basename='transportations') router.register(r'transportations', TransportationViewSet, basename='transportations')
router.register(r'notes', NoteViewSet, basename='notes') router.register(r'notes', NoteViewSet, basename='notes')
router.register(r'checklists', ChecklistViewSet, basename='checklists') router.register(r'checklists', ChecklistViewSet, basename='checklists')

View file

@ -1,4 +1,4 @@
from adventures.models import AdventureImage, Attachment from adventures.models import LocationImage, Attachment
protected_paths = ['images/', 'attachments/'] protected_paths = ['images/', 'attachments/']
@ -10,20 +10,20 @@ def checkFilePermission(fileId, user, mediaType):
# Construct the full relative path to match the database field # Construct the full relative path to match the database field
image_path = f"images/{fileId}" image_path = f"images/{fileId}"
# Fetch the AdventureImage object # Fetch the AdventureImage object
adventure = AdventureImage.objects.get(image=image_path).adventure location = LocationImage.objects.get(image=image_path).location
if adventure.is_public: if location.is_public:
return True return True
elif adventure.user_id == user: elif location.user == user:
return True return True
elif adventure.collections.exists(): elif location.collections.exists():
# Check if the user is in any collection's shared_with list # Check if the user is in any collection's shared_with list
for collection in adventure.collections.all(): for collection in location.collections.all():
if collection.shared_with.filter(id=user.id).exists(): if collection.shared_with.filter(id=user.id).exists():
return True return True
return False return False
else: else:
return False return False
except AdventureImage.DoesNotExist: except LocationImage.DoesNotExist:
return False return False
elif mediaType == 'attachments/': elif mediaType == 'attachments/':
try: try:
@ -31,14 +31,14 @@ def checkFilePermission(fileId, user, mediaType):
attachment_path = f"attachments/{fileId}" attachment_path = f"attachments/{fileId}"
# Fetch the Attachment object # Fetch the Attachment object
attachment = Attachment.objects.get(file=attachment_path) attachment = Attachment.objects.get(file=attachment_path)
adventure = attachment.adventure location = attachment.location
if adventure.is_public: if location.is_public:
return True return True
elif adventure.user_id == user: elif location.user == user:
return True return True
elif adventure.collections.exists(): elif location.collections.exists():
# Check if the user is in any collection's shared_with list # Check if the user is in any collection's shared_with list
for collection in adventure.collections.all(): for collection in location.collections.all():
if collection.shared_with.filter(id=user.id).exists(): if collection.shared_with.filter(id=user.id).exists():
return True return True
return False return False

View file

@ -0,0 +1,419 @@
TIMEZONES = [
"Africa/Abidjan",
"Africa/Accra",
"Africa/Addis_Ababa",
"Africa/Algiers",
"Africa/Asmera",
"Africa/Bamako",
"Africa/Bangui",
"Africa/Banjul",
"Africa/Bissau",
"Africa/Blantyre",
"Africa/Brazzaville",
"Africa/Bujumbura",
"Africa/Cairo",
"Africa/Casablanca",
"Africa/Ceuta",
"Africa/Conakry",
"Africa/Dakar",
"Africa/Dar_es_Salaam",
"Africa/Djibouti",
"Africa/Douala",
"Africa/El_Aaiun",
"Africa/Freetown",
"Africa/Gaborone",
"Africa/Harare",
"Africa/Johannesburg",
"Africa/Juba",
"Africa/Kampala",
"Africa/Khartoum",
"Africa/Kigali",
"Africa/Kinshasa",
"Africa/Lagos",
"Africa/Libreville",
"Africa/Lome",
"Africa/Luanda",
"Africa/Lubumbashi",
"Africa/Lusaka",
"Africa/Malabo",
"Africa/Maputo",
"Africa/Maseru",
"Africa/Mbabane",
"Africa/Mogadishu",
"Africa/Monrovia",
"Africa/Nairobi",
"Africa/Ndjamena",
"Africa/Niamey",
"Africa/Nouakchott",
"Africa/Ouagadougou",
"Africa/Porto-Novo",
"Africa/Sao_Tome",
"Africa/Tripoli",
"Africa/Tunis",
"Africa/Windhoek",
"America/Adak",
"America/Anchorage",
"America/Anguilla",
"America/Antigua",
"America/Araguaina",
"America/Argentina/La_Rioja",
"America/Argentina/Rio_Gallegos",
"America/Argentina/Salta",
"America/Argentina/San_Juan",
"America/Argentina/San_Luis",
"America/Argentina/Tucuman",
"America/Argentina/Ushuaia",
"America/Aruba",
"America/Asuncion",
"America/Bahia",
"America/Bahia_Banderas",
"America/Barbados",
"America/Belem",
"America/Belize",
"America/Blanc-Sablon",
"America/Boa_Vista",
"America/Bogota",
"America/Boise",
"America/Buenos_Aires",
"America/Cambridge_Bay",
"America/Campo_Grande",
"America/Cancun",
"America/Caracas",
"America/Catamarca",
"America/Cayenne",
"America/Cayman",
"America/Chicago",
"America/Chihuahua",
"America/Ciudad_Juarez",
"America/Coral_Harbour",
"America/Cordoba",
"America/Costa_Rica",
"America/Creston",
"America/Cuiaba",
"America/Curacao",
"America/Danmarkshavn",
"America/Dawson",
"America/Dawson_Creek",
"America/Denver",
"America/Detroit",
"America/Dominica",
"America/Edmonton",
"America/Eirunepe",
"America/El_Salvador",
"America/Fort_Nelson",
"America/Fortaleza",
"America/Glace_Bay",
"America/Godthab",
"America/Goose_Bay",
"America/Grand_Turk",
"America/Grenada",
"America/Guadeloupe",
"America/Guatemala",
"America/Guayaquil",
"America/Guyana",
"America/Halifax",
"America/Havana",
"America/Hermosillo",
"America/Indiana/Knox",
"America/Indiana/Marengo",
"America/Indiana/Petersburg",
"America/Indiana/Tell_City",
"America/Indiana/Vevay",
"America/Indiana/Vincennes",
"America/Indiana/Winamac",
"America/Indianapolis",
"America/Inuvik",
"America/Iqaluit",
"America/Jamaica",
"America/Jujuy",
"America/Juneau",
"America/Kentucky/Monticello",
"America/Kralendijk",
"America/La_Paz",
"America/Lima",
"America/Los_Angeles",
"America/Louisville",
"America/Lower_Princes",
"America/Maceio",
"America/Managua",
"America/Manaus",
"America/Marigot",
"America/Martinique",
"America/Matamoros",
"America/Mazatlan",
"America/Mendoza",
"America/Menominee",
"America/Merida",
"America/Metlakatla",
"America/Mexico_City",
"America/Miquelon",
"America/Moncton",
"America/Monterrey",
"America/Montevideo",
"America/Montserrat",
"America/Nassau",
"America/New_York",
"America/Nome",
"America/Noronha",
"America/North_Dakota/Beulah",
"America/North_Dakota/Center",
"America/North_Dakota/New_Salem",
"America/Ojinaga",
"America/Panama",
"America/Paramaribo",
"America/Phoenix",
"America/Port-au-Prince",
"America/Port_of_Spain",
"America/Porto_Velho",
"America/Puerto_Rico",
"America/Punta_Arenas",
"America/Rankin_Inlet",
"America/Recife",
"America/Regina",
"America/Resolute",
"America/Rio_Branco",
"America/Santarem",
"America/Santiago",
"America/Santo_Domingo",
"America/Sao_Paulo",
"America/Scoresbysund",
"America/Sitka",
"America/St_Barthelemy",
"America/St_Johns",
"America/St_Kitts",
"America/St_Lucia",
"America/St_Thomas",
"America/St_Vincent",
"America/Swift_Current",
"America/Tegucigalpa",
"America/Thule",
"America/Tijuana",
"America/Toronto",
"America/Tortola",
"America/Vancouver",
"America/Whitehorse",
"America/Winnipeg",
"America/Yakutat",
"Antarctica/Casey",
"Antarctica/Davis",
"Antarctica/DumontDUrville",
"Antarctica/Macquarie",
"Antarctica/Mawson",
"Antarctica/McMurdo",
"Antarctica/Palmer",
"Antarctica/Rothera",
"Antarctica/Syowa",
"Antarctica/Troll",
"Antarctica/Vostok",
"Arctic/Longyearbyen",
"Asia/Aden",
"Asia/Almaty",
"Asia/Amman",
"Asia/Anadyr",
"Asia/Aqtau",
"Asia/Aqtobe",
"Asia/Ashgabat",
"Asia/Atyrau",
"Asia/Baghdad",
"Asia/Bahrain",
"Asia/Baku",
"Asia/Bangkok",
"Asia/Barnaul",
"Asia/Beirut",
"Asia/Bishkek",
"Asia/Brunei",
"Asia/Calcutta",
"Asia/Chita",
"Asia/Colombo",
"Asia/Damascus",
"Asia/Dhaka",
"Asia/Dili",
"Asia/Dubai",
"Asia/Dushanbe",
"Asia/Famagusta",
"Asia/Gaza",
"Asia/Hebron",
"Asia/Hong_Kong",
"Asia/Hovd",
"Asia/Irkutsk",
"Asia/Jakarta",
"Asia/Jayapura",
"Asia/Jerusalem",
"Asia/Kabul",
"Asia/Kamchatka",
"Asia/Karachi",
"Asia/Katmandu",
"Asia/Khandyga",
"Asia/Krasnoyarsk",
"Asia/Kuala_Lumpur",
"Asia/Kuching",
"Asia/Kuwait",
"Asia/Macau",
"Asia/Magadan",
"Asia/Makassar",
"Asia/Manila",
"Asia/Muscat",
"Asia/Nicosia",
"Asia/Novokuznetsk",
"Asia/Novosibirsk",
"Asia/Omsk",
"Asia/Oral",
"Asia/Phnom_Penh",
"Asia/Pontianak",
"Asia/Pyongyang",
"Asia/Qatar",
"Asia/Qostanay",
"Asia/Qyzylorda",
"Asia/Rangoon",
"Asia/Riyadh",
"Asia/Saigon",
"Asia/Sakhalin",
"Asia/Samarkand",
"Asia/Seoul",
"Asia/Shanghai",
"Asia/Singapore",
"Asia/Srednekolymsk",
"Asia/Taipei",
"Asia/Tashkent",
"Asia/Tbilisi",
"Asia/Tehran",
"Asia/Thimphu",
"Asia/Tokyo",
"Asia/Tomsk",
"Asia/Ulaanbaatar",
"Asia/Urumqi",
"Asia/Ust-Nera",
"Asia/Vientiane",
"Asia/Vladivostok",
"Asia/Yakutsk",
"Asia/Yekaterinburg",
"Asia/Yerevan",
"Atlantic/Azores",
"Atlantic/Bermuda",
"Atlantic/Canary",
"Atlantic/Cape_Verde",
"Atlantic/Faeroe",
"Atlantic/Madeira",
"Atlantic/Reykjavik",
"Atlantic/South_Georgia",
"Atlantic/St_Helena",
"Atlantic/Stanley",
"Australia/Adelaide",
"Australia/Brisbane",
"Australia/Broken_Hill",
"Australia/Darwin",
"Australia/Eucla",
"Australia/Hobart",
"Australia/Lindeman",
"Australia/Lord_Howe",
"Australia/Melbourne",
"Australia/Perth",
"Australia/Sydney",
"Europe/Amsterdam",
"Europe/Andorra",
"Europe/Astrakhan",
"Europe/Athens",
"Europe/Belgrade",
"Europe/Berlin",
"Europe/Bratislava",
"Europe/Brussels",
"Europe/Bucharest",
"Europe/Budapest",
"Europe/Busingen",
"Europe/Chisinau",
"Europe/Copenhagen",
"Europe/Dublin",
"Europe/Gibraltar",
"Europe/Guernsey",
"Europe/Helsinki",
"Europe/Isle_of_Man",
"Europe/Istanbul",
"Europe/Jersey",
"Europe/Kaliningrad",
"Europe/Kiev",
"Europe/Kirov",
"Europe/Lisbon",
"Europe/Ljubljana",
"Europe/London",
"Europe/Luxembourg",
"Europe/Madrid",
"Europe/Malta",
"Europe/Mariehamn",
"Europe/Minsk",
"Europe/Monaco",
"Europe/Moscow",
"Europe/Oslo",
"Europe/Paris",
"Europe/Podgorica",
"Europe/Prague",
"Europe/Riga",
"Europe/Rome",
"Europe/Samara",
"Europe/San_Marino",
"Europe/Sarajevo",
"Europe/Saratov",
"Europe/Simferopol",
"Europe/Skopje",
"Europe/Sofia",
"Europe/Stockholm",
"Europe/Tallinn",
"Europe/Tirane",
"Europe/Ulyanovsk",
"Europe/Vaduz",
"Europe/Vatican",
"Europe/Vienna",
"Europe/Vilnius",
"Europe/Volgograd",
"Europe/Warsaw",
"Europe/Zagreb",
"Europe/Zurich",
"Indian/Antananarivo",
"Indian/Chagos",
"Indian/Christmas",
"Indian/Cocos",
"Indian/Comoro",
"Indian/Kerguelen",
"Indian/Mahe",
"Indian/Maldives",
"Indian/Mauritius",
"Indian/Mayotte",
"Indian/Reunion",
"Pacific/Apia",
"Pacific/Auckland",
"Pacific/Bougainville",
"Pacific/Chatham",
"Pacific/Easter",
"Pacific/Efate",
"Pacific/Enderbury",
"Pacific/Fakaofo",
"Pacific/Fiji",
"Pacific/Funafuti",
"Pacific/Galapagos",
"Pacific/Gambier",
"Pacific/Guadalcanal",
"Pacific/Guam",
"Pacific/Honolulu",
"Pacific/Kiritimati",
"Pacific/Kosrae",
"Pacific/Kwajalein",
"Pacific/Majuro",
"Pacific/Marquesas",
"Pacific/Midway",
"Pacific/Nauru",
"Pacific/Niue",
"Pacific/Norfolk",
"Pacific/Noumea",
"Pacific/Pago_Pago",
"Pacific/Palau",
"Pacific/Pitcairn",
"Pacific/Ponape",
"Pacific/Port_Moresby",
"Pacific/Rarotonga",
"Pacific/Saipan",
"Pacific/Tahiti",
"Pacific/Tarawa",
"Pacific/Tongatapu",
"Pacific/Truk",
"Pacific/Wake",
"Pacific/Wallis"
]

View file

@ -1,6 +1,6 @@
from .activity_types_view import * from .tags_view import *
from .adventure_image_view import * from .location_image_view import *
from .adventure_view import * from .location_view import *
from .category_view import * from .category_view import *
from .checklist_view import * from .checklist_view import *
from .collection_view import * from .collection_view import *

View file

@ -2,7 +2,7 @@ from rest_framework import viewsets, status
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response from rest_framework.response import Response
from adventures.models import Adventure, Attachment from adventures.models import Location, Attachment
from adventures.serializers import AttachmentSerializer from adventures.serializers import AttachmentSerializer
class AttachmentViewSet(viewsets.ModelViewSet): class AttachmentViewSet(viewsets.ModelViewSet):
@ -10,47 +10,43 @@ class AttachmentViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
def get_queryset(self): def get_queryset(self):
return Attachment.objects.filter(user_id=self.request.user) return Attachment.objects.filter(user=self.request.user)
@action(detail=True, methods=['post'])
def attachment_delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
if not request.user.is_authenticated: if not request.user.is_authenticated:
return Response({"error": "User is not authenticated"}, status=status.HTTP_401_UNAUTHORIZED) return Response({"error": "User is not authenticated"}, status=status.HTTP_401_UNAUTHORIZED)
adventure_id = request.data.get('adventure') location_id = request.data.get('location')
try: try:
adventure = Adventure.objects.get(id=adventure_id) location = Location.objects.get(id=location_id)
except Adventure.DoesNotExist: except Location.DoesNotExist:
return Response({"error": "Adventure not found"}, status=status.HTTP_404_NOT_FOUND) return Response({"error": "Location not found"}, status=status.HTTP_404_NOT_FOUND)
if adventure.user_id != request.user: if location.user != request.user:
# Check if the adventure has any collections # Check if the location has any collections
if adventure.collections.exists(): if location.collections.exists():
# Check if the user is in the shared_with list of any of the adventure's collections # Check if the user is in the shared_with list of any of the location's collections
user_has_access = False user_has_access = False
for collection in adventure.collections.all(): for collection in location.collections.all():
if collection.shared_with.filter(id=request.user.id).exists(): if collection.shared_with.filter(id=request.user.id).exists():
user_has_access = True user_has_access = True
break break
if not user_has_access: if not user_has_access:
return Response({"error": "User does not have permission to access this adventure"}, status=status.HTTP_403_FORBIDDEN) return Response({"error": "User does not have permission to access this location"}, status=status.HTTP_403_FORBIDDEN)
else: else:
return Response({"error": "User does not own this adventure"}, status=status.HTTP_403_FORBIDDEN) return Response({"error": "User does not own this location"}, status=status.HTTP_403_FORBIDDEN)
return super().create(request, *args, **kwargs) return super().create(request, *args, **kwargs)
def perform_create(self, serializer): def perform_create(self, serializer):
adventure_id = self.request.data.get('adventure') location_id = self.request.data.get('location')
adventure = Adventure.objects.get(id=adventure_id) location = Location.objects.get(id=location_id)
# If the adventure belongs to collections, set the owner to the collection owner # If the location belongs to collections, set the owner to the collection owner
if adventure.collections.exists(): if location.collections.exists():
# Get the first collection's owner (assuming all collections have the same owner) # Get the first collection's owner (assuming all collections have the same owner)
collection = adventure.collections.first() collection = location.collections.first()
serializer.save(user_id=collection.user_id) serializer.save(user=collection.user)
else: else:
# Otherwise, set the owner to the request user # Otherwise, set the owner to the request user
serializer.save(user_id=self.request.user) serializer.save(user=self.request.user)

View file

@ -2,21 +2,19 @@ from rest_framework import viewsets
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response from rest_framework.response import Response
from adventures.models import Category, Adventure from adventures.models import Category, Location
from adventures.serializers import CategorySerializer from adventures.serializers import CategorySerializer
class CategoryViewSet(viewsets.ModelViewSet): class CategoryViewSet(viewsets.ModelViewSet):
queryset = Category.objects.all()
serializer_class = CategorySerializer serializer_class = CategorySerializer
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
def get_queryset(self): def get_queryset(self):
return Category.objects.filter(user_id=self.request.user) return Category.objects.filter(user=self.request.user)
@action(detail=False, methods=['get']) def list(self, request, *args, **kwargs):
def categories(self, request):
""" """
Retrieve a list of distinct categories for adventures associated with the current user. Retrieve a list of distinct categories for locations associated with the current user.
""" """
categories = self.get_queryset().distinct() categories = self.get_queryset().distinct()
serializer = self.get_serializer(categories, many=True) serializer = self.get_serializer(categories, many=True)
@ -24,19 +22,19 @@ class CategoryViewSet(viewsets.ModelViewSet):
def destroy(self, request, *args, **kwargs): def destroy(self, request, *args, **kwargs):
instance = self.get_object() instance = self.get_object()
if instance.user_id != request.user: if instance.user != request.user:
return Response({"error": "User does not own this category"}, status return Response({"error": "User does not own this category"}, status
=400) =400)
if instance.name == 'general': if instance.name == 'general':
return Response({"error": "Cannot delete the general category"}, status=400) return Response({"error": "Cannot delete the general category"}, status=400)
# set any adventures with this category to a default category called general before deleting the category, if general does not exist create it for the user # set any locations with this category to a default category called general before deleting the category, if general does not exist create it for the user
general_category = Category.objects.filter(user_id=request.user, name='general').first() general_category = Category.objects.filter(user=request.user, name='general').first()
if not general_category: if not general_category:
general_category = Category.objects.create(user_id=request.user, name='general', icon='🌍', display_name='General') general_category = Category.objects.create(user=request.user, name='general', icon='🌍', display_name='General')
Adventure.objects.filter(category=instance).update(category=general_category) Location.objects.filter(category=instance).update(category=general_category)
return super().destroy(request, *args, **kwargs) return super().destroy(request, *args, **kwargs)

View file

@ -6,32 +6,22 @@ from adventures.models import Checklist
from adventures.serializers import ChecklistSerializer from adventures.serializers import ChecklistSerializer
from rest_framework.exceptions import PermissionDenied from rest_framework.exceptions import PermissionDenied
from adventures.permissions import IsOwnerOrSharedWithFullAccess from adventures.permissions import IsOwnerOrSharedWithFullAccess
from rest_framework.permissions import IsAuthenticated
class ChecklistViewSet(viewsets.ModelViewSet): class ChecklistViewSet(viewsets.ModelViewSet):
queryset = Checklist.objects.all()
serializer_class = ChecklistSerializer serializer_class = ChecklistSerializer
permission_classes = [IsOwnerOrSharedWithFullAccess] permission_classes = [IsAuthenticated, IsOwnerOrSharedWithFullAccess]
filterset_fields = ['is_public', 'collection'] filterset_fields = ['is_public', 'collection']
# return error message if user is not authenticated on the root endpoint
def list(self, request, *args, **kwargs): def list(self, request, *args, **kwargs):
# Prevent listing all adventures
return Response({"detail": "Listing all checklists is not allowed."},
status=status.HTTP_403_FORBIDDEN)
@action(detail=False, methods=['get'])
def all(self, request):
if not request.user.is_authenticated:
return Response({"error": "User is not authenticated"}, status=400)
queryset = Checklist.objects.filter( queryset = Checklist.objects.filter(
Q(user_id=request.user.id) Q(user=request.user)
) )
serializer = self.get_serializer(queryset, many=True) serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data) return Response(serializer.data)
def get_queryset(self): def get_queryset(self):
# if the user is not authenticated return only public transportations for retrieve action # if the user is not authenticated return only public checklists for retrieve action
if not self.request.user.is_authenticated: if not self.request.user.is_authenticated:
if self.action == 'retrieve': if self.action == 'retrieve':
return Checklist.objects.filter(is_public=True).distinct().order_by('-updated_at') return Checklist.objects.filter(is_public=True).distinct().order_by('-updated_at')
@ -39,14 +29,14 @@ class ChecklistViewSet(viewsets.ModelViewSet):
if self.action == 'retrieve': if self.action == 'retrieve':
# For individual adventure retrieval, include public adventures # For individual adventure retrieval, include public locations
return Checklist.objects.filter( return Checklist.objects.filter(
Q(is_public=True) | Q(user_id=self.request.user.id) | Q(collection__shared_with=self.request.user) Q(is_public=True) | Q(user=self.request.user) | Q(collection__shared_with=self.request.user)
).distinct().order_by('-updated_at') ).distinct().order_by('-updated_at')
else: else:
# For other actions, include user's own adventures and shared adventures # For other actions, include user's own locations and shared locations
return Checklist.objects.filter( return Checklist.objects.filter(
Q(user_id=self.request.user.id) | Q(collection__shared_with=self.request.user) Q(user=self.request.user) | Q(collection__shared_with=self.request.user)
).distinct().order_by('-updated_at') ).distinct().order_by('-updated_at')
def partial_update(self, request, *args, **kwargs): def partial_update(self, request, *args, **kwargs):
@ -65,11 +55,11 @@ class ChecklistViewSet(viewsets.ModelViewSet):
if new_collection is not None and new_collection!=instance.collection: if new_collection is not None and new_collection!=instance.collection:
# Check if the user is the owner of the new collection # Check if the user is the owner of the new collection
if new_collection.user_id != user or instance.user_id != user: if new_collection.user != user or instance.user != user:
raise PermissionDenied("You do not have permission to use this collection.") raise PermissionDenied("You do not have permission to use this collection.")
elif new_collection is None: elif new_collection is None:
# Handle the case where the user is trying to set the collection to None # Handle the case where the user is trying to set the collection to None
if instance.collection is not None and instance.collection.user_id != user: if instance.collection is not None and instance.collection.user != user:
raise PermissionDenied("You cannot remove the collection as you are not the owner.") raise PermissionDenied("You cannot remove the collection as you are not the owner.")
# Perform the update # Perform the update
@ -94,11 +84,11 @@ class ChecklistViewSet(viewsets.ModelViewSet):
if new_collection is not None and new_collection!=instance.collection: if new_collection is not None and new_collection!=instance.collection:
# Check if the user is the owner of the new collection # Check if the user is the owner of the new collection
if new_collection.user_id != user or instance.user_id != user: if new_collection.user != user or instance.user != user:
raise PermissionDenied("You do not have permission to use this collection.") raise PermissionDenied("You do not have permission to use this collection.")
elif new_collection is None: elif new_collection is None:
# Handle the case where the user is trying to set the collection to None # Handle the case where the user is trying to set the collection to None
if instance.collection is not None and instance.collection.user_id != user: if instance.collection is not None and instance.collection.user != user:
raise PermissionDenied("You cannot remove the collection as you are not the owner.") raise PermissionDenied("You cannot remove the collection as you are not the owner.")
# Perform the update # Perform the update
@ -119,12 +109,12 @@ class ChecklistViewSet(viewsets.ModelViewSet):
if collection: if collection:
user = self.request.user user = self.request.user
# Check if the user is the owner or is in the shared_with list # Check if the user is the owner or is in the shared_with list
if collection.user_id != user and not collection.shared_with.filter(id=user.id).exists(): if collection.user != user and not collection.shared_with.filter(id=user.id).exists():
# Return an error response if the user does not have permission # Return an error response if the user does not have permission
raise PermissionDenied("You do not have permission to use this collection.") raise PermissionDenied("You do not have permission to use this collection.")
# if collection the owner of the adventure is the owner of the collection # if collection the owner of the adventure is the owner of the collection
serializer.save(user_id=collection.user_id) serializer.save(user=collection.user)
return return
# Save the adventure with the current user as the owner # Save the adventure with the current user as the owner
serializer.save(user_id=self.request.user) serializer.save(user=self.request.user)

View file

@ -4,7 +4,7 @@ from django.db import transaction
from rest_framework import viewsets from rest_framework import viewsets
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.response import Response from rest_framework.response import Response
from adventures.models import Collection, Adventure, Transportation, Note, Checklist from adventures.models import Collection, Location, Transportation, Note, Checklist
from adventures.permissions import CollectionShared from adventures.permissions import CollectionShared
from adventures.serializers import CollectionSerializer from adventures.serializers import CollectionSerializer
from users.models import CustomUser as User from users.models import CustomUser as User
@ -15,8 +15,6 @@ class CollectionViewSet(viewsets.ModelViewSet):
permission_classes = [CollectionShared] permission_classes = [CollectionShared]
pagination_class = pagination.StandardResultsSetPagination pagination_class = pagination.StandardResultsSetPagination
# def get_queryset(self):
# return Collection.objects.filter(Q(user_id=self.request.user.id) & Q(is_archived=False))
def apply_sorting(self, queryset): def apply_sorting(self, queryset):
order_by = self.request.query_params.get('order_by', 'name') order_by = self.request.query_params.get('order_by', 'name')
@ -47,15 +45,13 @@ class CollectionViewSet(viewsets.ModelViewSet):
if order_direction == 'asc': if order_direction == 'asc':
ordering = '-updated_at' ordering = '-updated_at'
#print(f"Ordering by: {ordering}") # For debugging
return queryset.order_by(ordering) return queryset.order_by(ordering)
def list(self, request, *args, **kwargs): def list(self, request, *args, **kwargs):
# make sure the user is authenticated # make sure the user is authenticated
if not request.user.is_authenticated: if not request.user.is_authenticated:
return Response({"error": "User is not authenticated"}, status=400) return Response({"error": "User is not authenticated"}, status=400)
queryset = Collection.objects.filter(user_id=request.user.id, is_archived=False) queryset = Collection.objects.filter(user=request.user, is_archived=False)
queryset = self.apply_sorting(queryset) queryset = self.apply_sorting(queryset)
collections = self.paginate_and_respond(queryset, request) collections = self.paginate_and_respond(queryset, request)
return collections return collections
@ -66,7 +62,7 @@ class CollectionViewSet(viewsets.ModelViewSet):
return Response({"error": "User is not authenticated"}, status=400) return Response({"error": "User is not authenticated"}, status=400)
queryset = Collection.objects.filter( queryset = Collection.objects.filter(
Q(user_id=request.user.id) Q(user=request.user)
) )
queryset = self.apply_sorting(queryset) queryset = self.apply_sorting(queryset)
@ -80,7 +76,7 @@ class CollectionViewSet(viewsets.ModelViewSet):
return Response({"error": "User is not authenticated"}, status=400) return Response({"error": "User is not authenticated"}, status=400)
queryset = Collection.objects.filter( queryset = Collection.objects.filter(
Q(user_id=request.user.id) & Q(is_archived=True) Q(user=request.user.id) & Q(is_archived=True)
) )
queryset = self.apply_sorting(queryset) queryset = self.apply_sorting(queryset)
@ -88,7 +84,7 @@ class CollectionViewSet(viewsets.ModelViewSet):
return Response(serializer.data) return Response(serializer.data)
# this make the is_public field of the collection cascade to the adventures # this make the is_public field of the collection cascade to the locations
@transaction.atomic @transaction.atomic
def update(self, request, *args, **kwargs): def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False) partial = kwargs.pop('partial', False)
@ -99,7 +95,7 @@ class CollectionViewSet(viewsets.ModelViewSet):
if 'collection' in serializer.validated_data: if 'collection' in serializer.validated_data:
new_collection = serializer.validated_data['collection'] new_collection = serializer.validated_data['collection']
# if the new collection is different from the old one and the user making the request is not the owner of the new collection return an error # if the new collection is different from the old one and the user making the request is not the owner of the new collection return an error
if new_collection != instance.collection and new_collection.user_id != request.user: if new_collection != instance.collection and new_collection.user != request.user:
return Response({"error": "User does not own the new collection"}, status=400) return Response({"error": "User does not own the new collection"}, status=400)
# Check if the 'is_public' field is present in the update data # Check if the 'is_public' field is present in the update data
@ -107,29 +103,29 @@ class CollectionViewSet(viewsets.ModelViewSet):
new_public_status = serializer.validated_data['is_public'] new_public_status = serializer.validated_data['is_public']
# if is_public has changed and the user is not the owner of the collection return an error # if is_public has changed and the user is not the owner of the collection return an error
if new_public_status != instance.is_public and instance.user_id != request.user: if new_public_status != instance.is_public and instance.user != request.user:
print(f"User {request.user.id} does not own the collection {instance.id} that is owned by {instance.user_id}") print(f"User {request.user.id} does not own the collection {instance.id} that is owned by {instance.user}")
return Response({"error": "User does not own the collection"}, status=400) return Response({"error": "User does not own the collection"}, status=400)
# Get all adventures in this collection # Get all locations in this collection
adventures_in_collection = Adventure.objects.filter(collections=instance) locations_in_collection = Location.objects.filter(collections=instance)
if new_public_status: if new_public_status:
# If collection becomes public, make all adventures public # If collection becomes public, make all locations public
adventures_in_collection.update(is_public=True) locations_in_collection.update(is_public=True)
else: else:
# If collection becomes private, check each adventure # If collection becomes private, check each location
# Only set an adventure to private if ALL of its collections are private # Only set a location to private if ALL of its collections are private
# Collect adventures that do NOT belong to any other public collection (excluding the current one) # Collect locations that do NOT belong to any other public collection (excluding the current one)
adventure_ids_to_set_private = [] location_ids_to_set_private = []
for adventure in adventures_in_collection: for location in locations_in_collection:
has_public_collection = adventure.collections.filter(is_public=True).exclude(id=instance.id).exists() has_public_collection = location.collections.filter(is_public=True).exclude(id=instance.id).exists()
if not has_public_collection: if not has_public_collection:
adventure_ids_to_set_private.append(adventure.id) location_ids_to_set_private.append(location.id)
# Bulk update those adventures # Bulk update those locations
Adventure.objects.filter(id__in=adventure_ids_to_set_private).update(is_public=False) Location.objects.filter(id__in=location_ids_to_set_private).update(is_public=False)
# Update transportations, notes, and checklists related to this collection # Update transportations, notes, and checklists related to this collection
# These still use direct ForeignKey relationships # These still use direct ForeignKey relationships
@ -150,7 +146,7 @@ class CollectionViewSet(viewsets.ModelViewSet):
return Response(serializer.data) return Response(serializer.data)
# make an action to retreive all adventures that are shared with the user # make an action to retreive all locations that are shared with the user
@action(detail=False, methods=['get']) @action(detail=False, methods=['get'])
def shared(self, request): def shared(self, request):
if not request.user.is_authenticated: if not request.user.is_authenticated:
@ -162,7 +158,7 @@ class CollectionViewSet(viewsets.ModelViewSet):
serializer = self.get_serializer(queryset, many=True) serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data) return Response(serializer.data)
# Adds a new user to the shared_with field of an adventure # Adds a new user to the shared_with field of a location
@action(detail=True, methods=['post'], url_path='share/(?P<uuid>[^/.]+)') @action(detail=True, methods=['post'], url_path='share/(?P<uuid>[^/.]+)')
def share(self, request, pk=None, uuid=None): def share(self, request, pk=None, uuid=None):
collection = self.get_object() collection = self.get_object()
@ -177,7 +173,7 @@ class CollectionViewSet(viewsets.ModelViewSet):
return Response({"error": "Cannot share with yourself"}, status=400) return Response({"error": "Cannot share with yourself"}, status=400)
if collection.shared_with.filter(id=user.id).exists(): if collection.shared_with.filter(id=user.id).exists():
return Response({"error": "Adventure is already shared with this user"}, status=400) return Response({"error": "Location is already shared with this user"}, status=400)
collection.shared_with.add(user) collection.shared_with.add(user)
collection.save() collection.save()
@ -207,28 +203,28 @@ class CollectionViewSet(viewsets.ModelViewSet):
def get_queryset(self): def get_queryset(self):
if self.action == 'destroy': if self.action == 'destroy':
return Collection.objects.filter(user_id=self.request.user.id) return Collection.objects.filter(user=self.request.user.id)
if self.action in ['update', 'partial_update']: if self.action in ['update', 'partial_update']:
return Collection.objects.filter( return Collection.objects.filter(
Q(user_id=self.request.user.id) | Q(shared_with=self.request.user) Q(user=self.request.user.id) | Q(shared_with=self.request.user)
).distinct() ).distinct()
if self.action == 'retrieve': if self.action == 'retrieve':
if not self.request.user.is_authenticated: if not self.request.user.is_authenticated:
return Collection.objects.filter(is_public=True) return Collection.objects.filter(is_public=True)
return Collection.objects.filter( return Collection.objects.filter(
Q(is_public=True) | Q(user_id=self.request.user.id) | Q(shared_with=self.request.user) Q(is_public=True) | Q(user=self.request.user.id) | Q(shared_with=self.request.user)
).distinct() ).distinct()
# For list action, include collections owned by the user or shared with the user, that are not archived # For list action, include collections owned by the user or shared with the user, that are not archived
return Collection.objects.filter( return Collection.objects.filter(
(Q(user_id=self.request.user.id) | Q(shared_with=self.request.user)) & Q(is_archived=False) (Q(user=self.request.user.id) | Q(shared_with=self.request.user)) & Q(is_archived=False)
).distinct() ).distinct()
def perform_create(self, serializer): def perform_create(self, serializer):
# This is ok because you cannot share a collection when creating it # This is ok because you cannot share a collection when creating it
serializer.save(user_id=self.request.user) serializer.save(user=self.request.user)
def paginate_and_respond(self, queryset, request): def paginate_and_respond(self, queryset, request):
paginator = self.pagination_class() paginator = self.pagination_class()

View file

@ -3,8 +3,8 @@ from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
from django.db.models import Q from django.db.models import Q
from django.contrib.postgres.search import SearchVector, SearchQuery from django.contrib.postgres.search import SearchVector, SearchQuery
from adventures.models import Adventure, Collection from adventures.models import Location, Collection
from adventures.serializers import AdventureSerializer, CollectionSerializer from adventures.serializers import LocationSerializer, CollectionSerializer
from worldtravel.models import Country, Region, City, VisitedCity, VisitedRegion from worldtravel.models import Country, Region, City, VisitedCity, VisitedRegion
from worldtravel.serializers import CountrySerializer, RegionSerializer, CitySerializer, VisitedCitySerializer, VisitedRegionSerializer from worldtravel.serializers import CountrySerializer, RegionSerializer, CitySerializer, VisitedCitySerializer, VisitedRegionSerializer
from users.models import CustomUser as User from users.models import CustomUser as User
@ -20,7 +20,7 @@ class GlobalSearchView(viewsets.ViewSet):
# Initialize empty results # Initialize empty results
results = { results = {
"adventures": [], "locations": [],
"collections": [], "collections": [],
"users": [], "users": [],
"countries": [], "countries": [],
@ -30,15 +30,15 @@ class GlobalSearchView(viewsets.ViewSet):
"visited_cities": [] "visited_cities": []
} }
# Adventures: Full-Text Search # Locations: Full-Text Search
adventures = Adventure.objects.annotate( locations = Location.objects.annotate(
search=SearchVector('name', 'description', 'location') search=SearchVector('name', 'description', 'location')
).filter(search=SearchQuery(search_term), user_id=request.user) ).filter(search=SearchQuery(search_term), user=request.user)
results["adventures"] = AdventureSerializer(adventures, many=True).data results["locations"] = LocationSerializer(locations, many=True).data
# Collections: Partial Match Search # Collections: Partial Match Search
collections = Collection.objects.filter( collections = Collection.objects.filter(
Q(name__icontains=search_term) & Q(user_id=request.user) Q(name__icontains=search_term) & Q(user=request.user)
) )
results["collections"] = CollectionSerializer(collections, many=True).data results["collections"] = CollectionSerializer(collections, many=True).data
@ -64,10 +64,10 @@ class GlobalSearchView(viewsets.ViewSet):
results["cities"] = CitySerializer(cities, many=True).data results["cities"] = CitySerializer(cities, many=True).data
# Visited Regions and Cities # Visited Regions and Cities
visited_regions = VisitedRegion.objects.filter(user_id=request.user) visited_regions = VisitedRegion.objects.filter(user=request.user)
results["visited_regions"] = VisitedRegionSerializer(visited_regions, many=True).data results["visited_regions"] = VisitedRegionSerializer(visited_regions, many=True).data
visited_cities = VisitedCity.objects.filter(user_id=request.user) visited_cities = VisitedCity.objects.filter(user=request.user)
results["visited_cities"] = VisitedCitySerializer(visited_cities, many=True).data results["visited_cities"] = VisitedCitySerializer(visited_cities, many=True).data
return Response(results) return Response(results)

View file

@ -4,27 +4,26 @@ from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
from icalendar import Calendar, Event, vText, vCalAddress from icalendar import Calendar, Event, vText, vCalAddress
from datetime import datetime, timedelta from datetime import datetime, timedelta
from adventures.models import Adventure from adventures.models import Location
from adventures.serializers import AdventureSerializer from adventures.serializers import LocationSerializer
class IcsCalendarGeneratorViewSet(viewsets.ViewSet): class IcsCalendarGeneratorViewSet(viewsets.ViewSet):
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
@action(detail=False, methods=['get']) @action(detail=False, methods=['get'])
def generate(self, request): def generate(self, request):
adventures = Adventure.objects.filter(user_id=request.user) locations = Location.objects.filter(user=request.user)
serializer = AdventureSerializer(adventures, many=True) serializer = LocationSerializer(locations, many=True)
user = request.user user = request.user
name = f"{user.first_name} {user.last_name}" name = f"{user.first_name} {user.last_name}"
print(serializer.data)
cal = Calendar() cal = Calendar()
cal.add('prodid', '-//My Adventure Calendar//example.com//') cal.add('prodid', '-//My Adventure Calendar//example.com//')
cal.add('version', '2.0') cal.add('version', '2.0')
for adventure in serializer.data: for location in serializer.data:
if adventure['visits']: if location['visits']:
for visit in adventure['visits']: for visit in location['visits']:
# Skip if start_date is missing # Skip if start_date is missing
if not visit.get('start_date'): if not visit.get('start_date'):
continue continue
@ -42,7 +41,7 @@ class IcsCalendarGeneratorViewSet(viewsets.ViewSet):
# Create event # Create event
event = Event() event = Event()
event.add('summary', adventure['name']) event.add('summary', location['name'])
event.add('dtstart', start_date) event.add('dtstart', start_date)
event.add('dtend', end_date) event.add('dtend', end_date)
event.add('dtstamp', datetime.now()) event.add('dtstamp', datetime.now())
@ -50,11 +49,11 @@ class IcsCalendarGeneratorViewSet(viewsets.ViewSet):
event.add('class', 'PUBLIC') event.add('class', 'PUBLIC')
event.add('created', datetime.now()) event.add('created', datetime.now())
event.add('last-modified', datetime.now()) event.add('last-modified', datetime.now())
event.add('description', adventure['description']) event.add('description', location['description'])
if adventure.get('location'): if location.get('location'):
event.add('location', adventure['location']) event.add('location', location['location'])
if adventure.get('link'): if location.get('link'):
event.add('url', adventure['link']) event.add('url', location['link'])
organizer = vCalAddress(f'MAILTO:{user.email}') organizer = vCalAddress(f'MAILTO:{user.email}')
organizer.params['cn'] = vText(name) organizer.params['cn'] = vText(name)

View file

@ -4,15 +4,14 @@ from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response from rest_framework.response import Response
from django.db.models import Q from django.db.models import Q
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
from adventures.models import Adventure, AdventureImage from adventures.models import Location, LocationImage
from adventures.serializers import AdventureImageSerializer from adventures.serializers import LocationImageSerializer
from integrations.models import ImmichIntegration from integrations.models import ImmichIntegration
import uuid import uuid
import requests import requests
import os
class AdventureImageViewSet(viewsets.ModelViewSet): class AdventureImageViewSet(viewsets.ModelViewSet):
serializer_class = AdventureImageSerializer serializer_class = LocationImageSerializer
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
@action(detail=True, methods=['post']) @action(detail=True, methods=['post'])
@ -21,21 +20,21 @@ class AdventureImageViewSet(viewsets.ModelViewSet):
@action(detail=True, methods=['post']) @action(detail=True, methods=['post'])
def toggle_primary(self, request, *args, **kwargs): def toggle_primary(self, request, *args, **kwargs):
# Makes the image the primary image for the adventure, if there is already a primary image linked to the adventure, it is set to false and the new image is set to true. make sure that the permission is set to the owner of the adventure # Makes the image the primary image for the location, if there is already a primary image linked to the location, it is set to false and the new image is set to true. make sure that the permission is set to the owner of the location
if not request.user.is_authenticated: if not request.user.is_authenticated:
return Response({"error": "User is not authenticated"}, status=status.HTTP_401_UNAUTHORIZED) return Response({"error": "User is not authenticated"}, status=status.HTTP_401_UNAUTHORIZED)
instance = self.get_object() instance = self.get_object()
adventure = instance.adventure location = instance.location
if adventure.user_id != request.user: if location.user != request.user:
return Response({"error": "User does not own this adventure"}, status=status.HTTP_403_FORBIDDEN) return Response({"error": "User does not own this location"}, status=status.HTTP_403_FORBIDDEN)
# Check if the image is already the primary image # Check if the image is already the primary image
if instance.is_primary: if instance.is_primary:
return Response({"error": "Image is already the primary image"}, status=status.HTTP_400_BAD_REQUEST) return Response({"error": "Image is already the primary image"}, status=status.HTTP_400_BAD_REQUEST)
# Set the current primary image to false # Set the current primary image to false
AdventureImage.objects.filter(adventure=adventure, is_primary=True).update(is_primary=False) LocationImage.objects.filter(location=location, is_primary=True).update(is_primary=False)
# Set the new image to true # Set the new image to true
instance.is_primary = True instance.is_primary = True
@ -45,29 +44,29 @@ class AdventureImageViewSet(viewsets.ModelViewSet):
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
if not request.user.is_authenticated: if not request.user.is_authenticated:
return Response({"error": "User is not authenticated"}, status=status.HTTP_401_UNAUTHORIZED) return Response({"error": "User is not authenticated"}, status=status.HTTP_401_UNAUTHORIZED)
adventure_id = request.data.get('adventure') location_id = request.data.get('location')
try: try:
adventure = Adventure.objects.get(id=adventure_id) location = Location.objects.get(id=location_id)
except Adventure.DoesNotExist: except Location.DoesNotExist:
return Response({"error": "Adventure not found"}, status=status.HTTP_404_NOT_FOUND) return Response({"error": "location not found"}, status=status.HTTP_404_NOT_FOUND)
if adventure.user_id != request.user: if location.user != request.user:
# Check if the adventure has any collections # Check if the location has any collections
if adventure.collections.exists(): if location.collections.exists():
# Check if the user is in the shared_with list of any of the adventure's collections # Check if the user is in the shared_with list of any of the location's collections
user_has_access = False user_has_access = False
for collection in adventure.collections.all(): for collection in location.collections.all():
if collection.shared_with.filter(id=request.user.id).exists(): if collection.shared_with.filter(id=request.user.id).exists():
user_has_access = True user_has_access = True
break break
if not user_has_access: if not user_has_access:
return Response({"error": "User does not have permission to access this adventure"}, status=status.HTTP_403_FORBIDDEN) return Response({"error": "User does not have permission to access this location"}, status=status.HTTP_403_FORBIDDEN)
else: else:
return Response({"error": "User does not own this adventure"}, status=status.HTTP_403_FORBIDDEN) return Response({"error": "User does not own this location"}, status=status.HTTP_403_FORBIDDEN)
# Handle Immich ID for shared users by downloading the image # Handle Immich ID for shared users by downloading the image
if (request.user != adventure.user_id and if (request.user != location.user and
'immich_id' in request.data and 'immich_id' in request.data and
request.data.get('immich_id')): request.data.get('immich_id')):
@ -75,7 +74,7 @@ class AdventureImageViewSet(viewsets.ModelViewSet):
# Get the shared user's Immich integration # Get the shared user's Immich integration
try: try:
user_integration = ImmichIntegration.objects.get(user_id=request.user) user_integration = ImmichIntegration.objects.get(user=request.user)
except ImmichIntegration.DoesNotExist: except ImmichIntegration.DoesNotExist:
return Response({ return Response({
"error": "No Immich integration found for your account. Please set up Immich integration first.", "error": "No Immich integration found for your account. Please set up Immich integration first.",
@ -122,8 +121,8 @@ class AdventureImageViewSet(viewsets.ModelViewSet):
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
# Save with the downloaded image # Save with the downloaded image
adventure = serializer.validated_data['adventure'] location = serializer.validated_data['location']
serializer.save(user_id=adventure.user_id, image=image_file) serializer.save(user=location.user, image=image_file)
return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.data, status=status.HTTP_201_CREATED)
@ -144,14 +143,14 @@ class AdventureImageViewSet(viewsets.ModelViewSet):
if not request.user.is_authenticated: if not request.user.is_authenticated:
return Response({"error": "User is not authenticated"}, status=status.HTTP_401_UNAUTHORIZED) return Response({"error": "User is not authenticated"}, status=status.HTTP_401_UNAUTHORIZED)
adventure_id = request.data.get('adventure') location_id = request.data.get('location')
try: try:
adventure = Adventure.objects.get(id=adventure_id) location = Location.objects.get(id=location_id)
except Adventure.DoesNotExist: except Location.DoesNotExist:
return Response({"error": "Adventure not found"}, status=status.HTTP_404_NOT_FOUND) return Response({"error": "location not found"}, status=status.HTTP_404_NOT_FOUND)
if adventure.user_id != request.user: if location.user != request.user:
return Response({"error": "User does not own this adventure"}, status=status.HTTP_403_FORBIDDEN) return Response({"error": "User does not own this location"}, status=status.HTTP_403_FORBIDDEN)
return super().update(request, *args, **kwargs) return super().update(request, *args, **kwargs)
@ -159,14 +158,13 @@ class AdventureImageViewSet(viewsets.ModelViewSet):
return super().perform_destroy(instance) return super().perform_destroy(instance)
def destroy(self, request, *args, **kwargs): def destroy(self, request, *args, **kwargs):
print("destroy")
if not request.user.is_authenticated: if not request.user.is_authenticated:
return Response({"error": "User is not authenticated"}, status=status.HTTP_401_UNAUTHORIZED) return Response({"error": "User is not authenticated"}, status=status.HTTP_401_UNAUTHORIZED)
instance = self.get_object() instance = self.get_object()
adventure = instance.adventure location = instance.location
if adventure.user_id != request.user: if location.user != request.user:
return Response({"error": "User does not own this adventure"}, status=status.HTTP_403_FORBIDDEN) return Response({"error": "User does not own this location"}, status=status.HTTP_403_FORBIDDEN)
return super().destroy(request, *args, **kwargs) return super().destroy(request, *args, **kwargs)
@ -175,27 +173,27 @@ class AdventureImageViewSet(viewsets.ModelViewSet):
return Response({"error": "User is not authenticated"}, status=status.HTTP_401_UNAUTHORIZED) return Response({"error": "User is not authenticated"}, status=status.HTTP_401_UNAUTHORIZED)
instance = self.get_object() instance = self.get_object()
adventure = instance.adventure location = instance.location
if adventure.user_id != request.user: if location.user != request.user:
return Response({"error": "User does not own this adventure"}, status=status.HTTP_403_FORBIDDEN) return Response({"error": "User does not own this location"}, status=status.HTTP_403_FORBIDDEN)
return super().partial_update(request, *args, **kwargs) return super().partial_update(request, *args, **kwargs)
@action(detail=False, methods=['GET'], url_path='(?P<adventure_id>[0-9a-f-]+)') @action(detail=False, methods=['GET'], url_path='(?P<location_id>[0-9a-f-]+)')
def adventure_images(self, request, adventure_id=None, *args, **kwargs): def location_images(self, request, location_id=None, *args, **kwargs):
if not request.user.is_authenticated: if not request.user.is_authenticated:
return Response({"error": "User is not authenticated"}, status=status.HTTP_401_UNAUTHORIZED) return Response({"error": "User is not authenticated"}, status=status.HTTP_401_UNAUTHORIZED)
try: try:
adventure_uuid = uuid.UUID(adventure_id) location_uuid = uuid.UUID(location_id)
except ValueError: except ValueError:
return Response({"error": "Invalid adventure ID"}, status=status.HTTP_400_BAD_REQUEST) return Response({"error": "Invalid location ID"}, status=status.HTTP_400_BAD_REQUEST)
# Updated queryset to include images from adventures the user owns OR has shared access to # Updated queryset to include images from locations the user owns OR has shared access to
queryset = AdventureImage.objects.filter( queryset = LocationImage.objects.filter(
Q(adventure__id=adventure_uuid) & ( Q(location__id=location_uuid) & (
Q(adventure__user_id=request.user) | # User owns the adventure Q(location__user=request.user) | # User owns the location
Q(adventure__collections__shared_with=request.user) # User has shared access via collection Q(location__collections__shared_with=request.user) # User has shared access via collection
) )
).distinct() ).distinct()
@ -203,13 +201,13 @@ class AdventureImageViewSet(viewsets.ModelViewSet):
return Response(serializer.data) return Response(serializer.data)
def get_queryset(self): def get_queryset(self):
# Updated to include images from adventures the user owns OR has shared access to # Updated to include images from locations the user owns OR has shared access to
return AdventureImage.objects.filter( return LocationImage.objects.filter(
Q(adventure__user_id=self.request.user) | # User owns the adventure Q(location__user=self.request.user) | # User owns the location
Q(adventure__collections__shared_with=self.request.user) # User has shared access via collection Q(location__collections__shared_with=self.request.user) # User has shared access via collection
).distinct() ).distinct()
def perform_create(self, serializer): def perform_create(self, serializer):
# Always set the image owner to the adventure owner, not the current user # Always set the image owner to the location owner, not the current user
adventure = serializer.validated_data['adventure'] location = serializer.validated_data['location']
serializer.save(user_id=adventure.user_id) serializer.save(user=location.user)

View file

@ -7,19 +7,18 @@ from rest_framework import viewsets, status
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.response import Response from rest_framework.response import Response
import requests import requests
from adventures.models import Location, Category
from adventures.models import Adventure, Category, Transportation, Lodging
from adventures.permissions import IsOwnerOrSharedWithFullAccess from adventures.permissions import IsOwnerOrSharedWithFullAccess
from adventures.serializers import AdventureSerializer, TransportationSerializer, LodgingSerializer from adventures.serializers import LocationSerializer
from adventures.utils import pagination from adventures.utils import pagination
class AdventureViewSet(viewsets.ModelViewSet): class LocationViewSet(viewsets.ModelViewSet):
""" """
ViewSet for managing Adventure objects with support for filtering, sorting, ViewSet for managing Adventure objects with support for filtering, sorting,
and sharing functionality. and sharing functionality.
""" """
serializer_class = AdventureSerializer serializer_class = LocationSerializer
permission_classes = [IsOwnerOrSharedWithFullAccess] permission_classes = [IsOwnerOrSharedWithFullAccess]
pagination_class = pagination.StandardResultsSetPagination pagination_class = pagination.StandardResultsSetPagination
@ -28,20 +27,20 @@ class AdventureViewSet(viewsets.ModelViewSet):
def get_queryset(self): def get_queryset(self):
""" """
Returns queryset based on user authentication and action type. Returns queryset based on user authentication and action type.
Public actions allow unauthenticated access to public adventures. Public actions allow unauthenticated access to public locations.
""" """
user = self.request.user user = self.request.user
public_allowed_actions = {'retrieve', 'additional_info'} public_allowed_actions = {'retrieve', 'additional_info'}
if not user.is_authenticated: if not user.is_authenticated:
if self.action in public_allowed_actions: if self.action in public_allowed_actions:
return Adventure.objects.retrieve_adventures( return Location.objects.retrieve_locations(
user, include_public=True user, include_public=True
).order_by('-updated_at') ).order_by('-updated_at')
return Adventure.objects.none() return Location.objects.none()
include_public = self.action in public_allowed_actions include_public = self.action in public_allowed_actions
return Adventure.objects.retrieve_adventures( return Location.objects.retrieve_locations(
user, user,
include_public=include_public, include_public=include_public,
include_owned=True, include_owned=True,
@ -67,7 +66,7 @@ class AdventureViewSet(viewsets.ModelViewSet):
# Apply sorting logic # Apply sorting logic
queryset = self._apply_ordering(queryset, order_by, order_direction) queryset = self._apply_ordering(queryset, order_by, order_direction)
# Filter adventures without collections if requested # Filter locations without collections if requested
if include_collections == 'false': if include_collections == 'false':
queryset = queryset.filter(collections__isnull=True) queryset = queryset.filter(collections__isnull=True)
@ -116,7 +115,7 @@ class AdventureViewSet(viewsets.ModelViewSet):
# Use the current user as owner since ManyToMany allows multiple collection owners # Use the current user as owner since ManyToMany allows multiple collection owners
user_to_assign = self.request.user user_to_assign = self.request.user
serializer.save(user_id=user_to_assign) serializer.save(user=user_to_assign)
def perform_update(self, serializer): def perform_update(self, serializer):
"""Update adventure.""" """Update adventure."""
@ -147,18 +146,18 @@ class AdventureViewSet(viewsets.ModelViewSet):
@action(detail=False, methods=['get']) @action(detail=False, methods=['get'])
def filtered(self, request): def filtered(self, request):
"""Filter adventures by category types and visit status.""" """Filter locations by category types and visit status."""
types = request.query_params.get('types', '').split(',') types = request.query_params.get('types', '').split(',')
# Handle 'all' types # Handle 'all' types
if 'all' in types: if 'all' in types:
types = Category.objects.filter( types = Category.objects.filter(
user_id=request.user user=request.user
).values_list('name', flat=True) ).values_list('name', flat=True)
else: else:
# Validate provided types # Validate provided types
if not types or not all( if not types or not all(
Category.objects.filter(user_id=request.user, name=type_name).exists() Category.objects.filter(user=request.user, name=type_name).exists()
for type_name in types for type_name in types
): ):
return Response( return Response(
@ -167,9 +166,9 @@ class AdventureViewSet(viewsets.ModelViewSet):
) )
# Build base queryset # Build base queryset
queryset = Adventure.objects.filter( queryset = Location.objects.filter(
category__in=Category.objects.filter(name__in=types, user_id=request.user), category__in=Category.objects.filter(name__in=types, user=request.user),
user_id=request.user.id user=request.user.id
) )
# Apply visit status filtering # Apply visit status filtering
@ -180,19 +179,19 @@ class AdventureViewSet(viewsets.ModelViewSet):
@action(detail=False, methods=['get']) @action(detail=False, methods=['get'])
def all(self, request): def all(self, request):
"""Get all adventures (public and owned) with optional collection filtering.""" """Get all locations (public and owned) with optional collection filtering."""
if not request.user.is_authenticated: if not request.user.is_authenticated:
return Response({"error": "User is not authenticated"}, status=400) return Response({"error": "User is not authenticated"}, status=400)
include_collections = request.query_params.get('include_collections', 'false') == 'true' include_collections = request.query_params.get('include_collections', 'false') == 'true'
# Build queryset with collection filtering # Build queryset with collection filtering
base_filter = Q(user_id=request.user.id) base_filter = Q(user=request.user.id)
if include_collections: if include_collections:
queryset = Adventure.objects.filter(base_filter) queryset = Location.objects.filter(base_filter)
else: else:
queryset = Adventure.objects.filter(base_filter, collections__isnull=True) queryset = Location.objects.filter(base_filter, collections__isnull=True)
queryset = self.apply_sorting(queryset) queryset = self.apply_sorting(queryset)
serializer = self.get_serializer(queryset, many=True) serializer = self.get_serializer(queryset, many=True)
@ -225,7 +224,7 @@ class AdventureViewSet(viewsets.ModelViewSet):
def _validate_collection_permissions(self, collections): def _validate_collection_permissions(self, collections):
"""Validate user has permission to use all provided collections. Only the owner or shared users can use collections.""" """Validate user has permission to use all provided collections. Only the owner or shared users can use collections."""
for collection in collections: for collection in collections:
if not (collection.user_id == self.request.user or if not (collection.user == self.request.user or
collection.shared_with.filter(uuid=self.request.user.uuid).exists()): collection.shared_with.filter(uuid=self.request.user.uuid).exists()):
raise PermissionDenied( raise PermissionDenied(
f"You do not have permission to use collection '{collection.name}'." f"You do not have permission to use collection '{collection.name}'."
@ -235,7 +234,7 @@ class AdventureViewSet(viewsets.ModelViewSet):
"""Validate permissions for collection updates (add/remove).""" """Validate permissions for collection updates (add/remove)."""
# Check permissions for new collections being added # Check permissions for new collections being added
for collection in new_collections: for collection in new_collections:
if (collection.user_id != self.request.user and if (collection.user != self.request.user and
not collection.shared_with.filter(uuid=self.request.user.uuid).exists()): not collection.shared_with.filter(uuid=self.request.user.uuid).exists()):
raise PermissionDenied( raise PermissionDenied(
f"You do not have permission to use collection '{collection.name}'." f"You do not have permission to use collection '{collection.name}'."
@ -247,7 +246,7 @@ class AdventureViewSet(viewsets.ModelViewSet):
collections_to_remove = current_collections - new_collections_set collections_to_remove = current_collections - new_collections_set
for collection in collections_to_remove: for collection in collections_to_remove:
if (collection.user_id != self.request.user and if (collection.user != self.request.user and
not collection.shared_with.filter(uuid=self.request.user.uuid).exists()): not collection.shared_with.filter(uuid=self.request.user.uuid).exists()):
raise PermissionDenied( raise PermissionDenied(
f"You cannot remove the adventure from collection '{collection.name}' " f"You cannot remove the adventure from collection '{collection.name}' "
@ -284,7 +283,7 @@ class AdventureViewSet(viewsets.ModelViewSet):
return True return True
# Check ownership # Check ownership
if user.is_authenticated and adventure.user_id == user: if user.is_authenticated and adventure.user == user:
return True return True
# Check shared collection access # Check shared collection access

View file

@ -17,7 +17,7 @@ class LodgingViewSet(viewsets.ModelViewSet):
if not request.user.is_authenticated: if not request.user.is_authenticated:
return Response(status=status.HTTP_403_FORBIDDEN) return Response(status=status.HTTP_403_FORBIDDEN)
queryset = Lodging.objects.filter( queryset = Lodging.objects.filter(
Q(user_id=request.user.id) Q(user=request.user.id)
) )
serializer = self.get_serializer(queryset, many=True) serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data) return Response(serializer.data)
@ -25,13 +25,13 @@ class LodgingViewSet(viewsets.ModelViewSet):
def get_queryset(self): def get_queryset(self):
user = self.request.user user = self.request.user
if self.action == 'retrieve': if self.action == 'retrieve':
# For individual adventure retrieval, include public adventures, user's own adventures and shared adventures # For individual adventure retrieval, include public locations, user's own locations and shared locations
return Lodging.objects.filter( return Lodging.objects.filter(
Q(is_public=True) | Q(user_id=user.id) | Q(collection__shared_with=user.id) Q(is_public=True) | Q(user=user.id) | Q(collection__shared_with=user.id)
).distinct().order_by('-updated_at') ).distinct().order_by('-updated_at')
# For other actions, include user's own adventures and shared adventures # For other actions, include user's own locations and shared locations
return Lodging.objects.filter( return Lodging.objects.filter(
Q(user_id=user.id) | Q(collection__shared_with=user.id) Q(user=user.id) | Q(collection__shared_with=user.id)
).distinct().order_by('-updated_at') ).distinct().order_by('-updated_at')
def partial_update(self, request, *args, **kwargs): def partial_update(self, request, *args, **kwargs):
@ -48,11 +48,11 @@ class LodgingViewSet(viewsets.ModelViewSet):
if new_collection is not None and new_collection != instance.collection: if new_collection is not None and new_collection != instance.collection:
# Check if the user is the owner of the new collection # Check if the user is the owner of the new collection
if new_collection.user_id != user or instance.user_id != user: if new_collection.user != user or instance.user != user:
raise PermissionDenied("You do not have permission to use this collection.") raise PermissionDenied("You do not have permission to use this collection.")
elif new_collection is None: elif new_collection is None:
# Handle the case where the user is trying to set the collection to None # Handle the case where the user is trying to set the collection to None
if instance.collection is not None and instance.collection.user_id != user: if instance.collection is not None and instance.collection.user != user:
raise PermissionDenied("You cannot remove the collection as you are not the owner.") raise PermissionDenied("You cannot remove the collection as you are not the owner.")
# Perform the update # Perform the update
@ -73,12 +73,12 @@ class LodgingViewSet(viewsets.ModelViewSet):
if collection: if collection:
user = self.request.user user = self.request.user
# Check if the user is the owner or is in the shared_with list # Check if the user is the owner or is in the shared_with list
if collection.user_id != user and not collection.shared_with.filter(id=user.id).exists(): if collection.user != user and not collection.shared_with.filter(id=user.id).exists():
# Return an error response if the user does not have permission # Return an error response if the user does not have permission
raise PermissionDenied("You do not have permission to use this collection.") raise PermissionDenied("You do not have permission to use this collection.")
# if collection the owner of the adventure is the owner of the collection # if collection the owner of the adventure is the owner of the collection
serializer.save(user_id=collection.user_id) serializer.save(user=collection.user)
return return
# Save the adventure with the current user as the owner # Save the adventure with the current user as the owner
serializer.save(user_id=self.request.user) serializer.save(user=self.request.user)

View file

@ -15,7 +15,7 @@ class NoteViewSet(viewsets.ModelViewSet):
# return error message if user is not authenticated on the root endpoint # return error message if user is not authenticated on the root endpoint
def list(self, request, *args, **kwargs): def list(self, request, *args, **kwargs):
# Prevent listing all adventures # Prevent listing all locations
return Response({"detail": "Listing all notes is not allowed."}, return Response({"detail": "Listing all notes is not allowed."},
status=status.HTTP_403_FORBIDDEN) status=status.HTTP_403_FORBIDDEN)
@ -24,7 +24,7 @@ class NoteViewSet(viewsets.ModelViewSet):
if not request.user.is_authenticated: if not request.user.is_authenticated:
return Response({"error": "User is not authenticated"}, status=400) return Response({"error": "User is not authenticated"}, status=400)
queryset = Note.objects.filter( queryset = Note.objects.filter(
Q(user_id=request.user.id) Q(user=request.user.id)
) )
serializer = self.get_serializer(queryset, many=True) serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data) return Response(serializer.data)
@ -39,14 +39,14 @@ class NoteViewSet(viewsets.ModelViewSet):
if self.action == 'retrieve': if self.action == 'retrieve':
# For individual adventure retrieval, include public adventures # For individual adventure retrieval, include public locations
return Note.objects.filter( return Note.objects.filter(
Q(is_public=True) | Q(user_id=self.request.user.id) | Q(collection__shared_with=self.request.user) Q(is_public=True) | Q(user=self.request.user.id) | Q(collection__shared_with=self.request.user)
).distinct().order_by('-updated_at') ).distinct().order_by('-updated_at')
else: else:
# For other actions, include user's own adventures and shared adventures # For other actions, include user's own locations and shared locations
return Note.objects.filter( return Note.objects.filter(
Q(user_id=self.request.user.id) | Q(collection__shared_with=self.request.user) Q(user=self.request.user.id) | Q(collection__shared_with=self.request.user)
).distinct().order_by('-updated_at') ).distinct().order_by('-updated_at')
def partial_update(self, request, *args, **kwargs): def partial_update(self, request, *args, **kwargs):
@ -65,11 +65,11 @@ class NoteViewSet(viewsets.ModelViewSet):
if new_collection is not None and new_collection!=instance.collection: if new_collection is not None and new_collection!=instance.collection:
# Check if the user is the owner of the new collection # Check if the user is the owner of the new collection
if new_collection.user_id != user or instance.user_id != user: if new_collection.user != user or instance.user != user:
raise PermissionDenied("You do not have permission to use this collection.") raise PermissionDenied("You do not have permission to use this collection.")
elif new_collection is None: elif new_collection is None:
# Handle the case where the user is trying to set the collection to None # Handle the case where the user is trying to set the collection to None
if instance.collection is not None and instance.collection.user_id != user: if instance.collection is not None and instance.collection.user != user:
raise PermissionDenied("You cannot remove the collection as you are not the owner.") raise PermissionDenied("You cannot remove the collection as you are not the owner.")
# Perform the update # Perform the update
@ -94,11 +94,11 @@ class NoteViewSet(viewsets.ModelViewSet):
if new_collection is not None and new_collection!=instance.collection: if new_collection is not None and new_collection!=instance.collection:
# Check if the user is the owner of the new collection # Check if the user is the owner of the new collection
if new_collection.user_id != user or instance.user_id != user: if new_collection.user != user or instance.user != user:
raise PermissionDenied("You do not have permission to use this collection.") raise PermissionDenied("You do not have permission to use this collection.")
elif new_collection is None: elif new_collection is None:
# Handle the case where the user is trying to set the collection to None # Handle the case where the user is trying to set the collection to None
if instance.collection is not None and instance.collection.user_id != user: if instance.collection is not None and instance.collection.user != user:
raise PermissionDenied("You cannot remove the collection as you are not the owner.") raise PermissionDenied("You cannot remove the collection as you are not the owner.")
# Perform the update # Perform the update
@ -119,12 +119,12 @@ class NoteViewSet(viewsets.ModelViewSet):
if collection: if collection:
user = self.request.user user = self.request.user
# Check if the user is the owner or is in the shared_with list # Check if the user is the owner or is in the shared_with list
if collection.user_id != user and not collection.shared_with.filter(id=user.id).exists(): if collection.user != user and not collection.shared_with.filter(id=user.id).exists():
# Return an error response if the user does not have permission # Return an error response if the user does not have permission
raise PermissionDenied("You do not have permission to use this collection.") raise PermissionDenied("You do not have permission to use this collection.")
# if collection the owner of the adventure is the owner of the collection # if collection the owner of the adventure is the owner of the collection
serializer.save(user_id=collection.user_id) serializer.save(user=collection.user)
return return
# Save the adventure with the current user as the owner # Save the adventure with the current user as the owner
serializer.save(user_id=self.request.user) serializer.save(user=self.request.user)

View file

@ -5,8 +5,6 @@ from rest_framework.response import Response
from django.conf import settings from django.conf import settings
import requests import requests
from geopy.distance import geodesic from geopy.distance import geodesic
import time
class RecommendationsViewSet(viewsets.ViewSet): class RecommendationsViewSet(viewsets.ViewSet):
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
@ -14,7 +12,7 @@ class RecommendationsViewSet(viewsets.ViewSet):
HEADERS = {'User-Agent': 'AdventureLog Server'} HEADERS = {'User-Agent': 'AdventureLog Server'}
def parse_google_places(self, places, origin): def parse_google_places(self, places, origin):
adventures = [] locations = []
for place in places: for place in places:
location = place.get('location', {}) location = place.get('location', {})
@ -45,16 +43,16 @@ class RecommendationsViewSet(viewsets.ViewSet):
"distance_km": round(distance_km, 2), "distance_km": round(distance_km, 2),
} }
adventures.append(adventure) locations.append(adventure)
# Sort by distance ascending # Sort by distance ascending
adventures.sort(key=lambda x: x["distance_km"]) locations.sort(key=lambda x: x["distance_km"])
return adventures return locations
def parse_overpass_response(self, data, request): def parse_overpass_response(self, data, request):
nodes = data.get('elements', []) nodes = data.get('elements', [])
adventures = [] locations = []
all = request.query_params.get('all', False) all = request.query_params.get('all', False)
origin = None origin = None
@ -102,13 +100,13 @@ class RecommendationsViewSet(viewsets.ViewSet):
"powered_by": "osm" "powered_by": "osm"
} }
adventures.append(adventure) locations.append(adventure)
# Sort by distance if available # Sort by distance if available
if origin: if origin:
adventures.sort(key=lambda x: x.get("distance_km") or float("inf")) locations.sort(key=lambda x: x.get("distance_km") or float("inf"))
return adventures return locations
def query_overpass(self, lat, lon, radius, category, request): def query_overpass(self, lat, lon, radius, category, request):
@ -172,8 +170,8 @@ class RecommendationsViewSet(viewsets.ViewSet):
print("Overpass API error:", e) print("Overpass API error:", e)
return Response({"error": "Failed to retrieve data from Overpass API."}, status=500) return Response({"error": "Failed to retrieve data from Overpass API."}, status=500)
adventures = self.parse_overpass_response(data, request) locations = self.parse_overpass_response(data, request)
return Response(adventures) return Response(locations)
def query_google_nearby(self, lat, lon, radius, category, request): def query_google_nearby(self, lat, lon, radius, category, request):
"""Query Google Places API (New) for nearby places""" """Query Google Places API (New) for nearby places"""
@ -216,9 +214,9 @@ class RecommendationsViewSet(viewsets.ViewSet):
places = data.get('places', []) places = data.get('places', [])
origin = (float(lat), float(lon)) origin = (float(lat), float(lon))
adventures = self.parse_google_places(places, origin) locations = self.parse_google_places(places, origin)
return Response(adventures) return Response(locations)
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
print(f"Google Places API error: {e}") print(f"Google Places API error: {e}")

View file

@ -3,11 +3,9 @@ from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response from rest_framework.response import Response
from worldtravel.models import Region, City, VisitedRegion, VisitedCity from worldtravel.models import Region, City, VisitedRegion, VisitedCity
from adventures.models import Adventure from adventures.models import Location
from adventures.serializers import AdventureSerializer from adventures.serializers import LocationSerializer
import requests
from adventures.geocoding import reverse_geocode from adventures.geocoding import reverse_geocode
from adventures.geocoding import extractIsoCode
from django.conf import settings from django.conf import settings
from adventures.geocoding import search_google, search_osm from adventures.geocoding import search_google, search_osm
@ -47,14 +45,14 @@ class ReverseGeocodeViewSet(viewsets.ViewSet):
@action(detail=False, methods=['post']) @action(detail=False, methods=['post'])
def mark_visited_region(self, request): def mark_visited_region(self, request):
# 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 # searches through all of the users locations, if the serialized data is_visited, is true, runs reverse geocode on the locations and if a region is found, marks it as visited. Use the extractIsoCode function to get the region
new_region_count = 0 new_region_count = 0
new_regions = {} new_regions = {}
new_city_count = 0 new_city_count = 0
new_cities = {} new_cities = {}
adventures = Adventure.objects.filter(user_id=self.request.user) locations = Location.objects.filter(user=self.request.user)
serializer = AdventureSerializer(adventures, many=True) serializer = LocationSerializer(locations, many=True)
for adventure, serialized_adventure in zip(adventures, serializer.data): for adventure, serialized_adventure in zip(locations, serializer.data):
if serialized_adventure['is_visited'] == True: if serialized_adventure['is_visited'] == True:
lat = adventure.latitude lat = adventure.latitude
lon = adventure.longitude lon = adventure.longitude
@ -69,18 +67,18 @@ class ReverseGeocodeViewSet(viewsets.ViewSet):
# data already contains region_id and city_id # data already contains region_id and city_id
if 'region_id' in data and data['region_id'] is not None: if 'region_id' in data and data['region_id'] is not None:
region = Region.objects.filter(id=data['region_id']).first() region = Region.objects.filter(id=data['region_id']).first()
visited_region = VisitedRegion.objects.filter(region=region, user_id=self.request.user).first() visited_region = VisitedRegion.objects.filter(region=region, user=self.request.user).first()
if not visited_region: if not visited_region:
visited_region = VisitedRegion(region=region, user_id=self.request.user) visited_region = VisitedRegion(region=region, user=self.request.user)
visited_region.save() visited_region.save()
new_region_count += 1 new_region_count += 1
new_regions[region.id] = region.name new_regions[region.id] = region.name
if 'city_id' in data and data['city_id'] is not None: if 'city_id' in data and data['city_id'] is not None:
city = City.objects.filter(id=data['city_id']).first() city = City.objects.filter(id=data['city_id']).first()
visited_city = VisitedCity.objects.filter(city=city, user_id=self.request.user).first() visited_city = VisitedCity.objects.filter(city=city, user=self.request.user).first()
if not visited_city: if not visited_city:
visited_city = VisitedCity(city=city, user_id=self.request.user) visited_city = VisitedCity(city=city, user=self.request.user)
visited_city.save() visited_city.save()
new_city_count += 1 new_city_count += 1
new_cities[city.id] = city.name new_cities[city.id] = city.name

View file

@ -1,11 +1,9 @@
from rest_framework import viewsets from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.decorators import action from rest_framework.decorators import action
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from worldtravel.models import City, Region, Country, VisitedCity, VisitedRegion from worldtravel.models import City, Region, Country, VisitedCity, VisitedRegion
from adventures.models import Adventure, Collection from adventures.models import Location, Collection
from users.serializers import CustomUserDetailsSerializer as PublicUserSerializer
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
User = get_user_model() User = get_user_model()
@ -26,21 +24,21 @@ class StatsViewSet(viewsets.ViewSet):
user.email = None user.email = None
# get the counts for the user # get the counts for the user
adventure_count = Adventure.objects.filter( location_count = Location.objects.filter(
user_id=user.id).count() user=user.id).count()
trips_count = Collection.objects.filter( trips_count = Collection.objects.filter(
user_id=user.id).count() user=user.id).count()
visited_city_count = VisitedCity.objects.filter( visited_city_count = VisitedCity.objects.filter(
user_id=user.id).count() user=user.id).count()
total_cities = City.objects.count() total_cities = City.objects.count()
visited_region_count = VisitedRegion.objects.filter( visited_region_count = VisitedRegion.objects.filter(
user_id=user.id).count() user=user.id).count()
total_regions = Region.objects.count() total_regions = Region.objects.count()
visited_country_count = VisitedRegion.objects.filter( visited_country_count = VisitedRegion.objects.filter(
user_id=user.id).values('region__country').distinct().count() user=user.id).values('region__country').distinct().count()
total_countries = Country.objects.count() total_countries = Country.objects.count()
return Response({ return Response({
'adventure_count': adventure_count, 'location_count': location_count,
'trips_count': trips_count, 'trips_count': trips_count,
'visited_city_count': visited_city_count, 'visited_city_count': visited_city_count,
'total_cities': total_cities, 'total_cities': total_cities,

View file

@ -2,7 +2,7 @@ from rest_framework import viewsets
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response from rest_framework.response import Response
from adventures.models import Adventure from adventures.models import Location
class ActivityTypesView(viewsets.ViewSet): class ActivityTypesView(viewsets.ViewSet):
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
@ -10,7 +10,7 @@ class ActivityTypesView(viewsets.ViewSet):
@action(detail=False, methods=['get']) @action(detail=False, methods=['get'])
def types(self, request): def types(self, request):
""" """
Retrieve a list of distinct activity types for adventures associated with the current user. Retrieve a list of distinct activity types for locations associated with the current user.
Args: Args:
request (HttpRequest): The HTTP request object. request (HttpRequest): The HTTP request object.
@ -18,7 +18,7 @@ class ActivityTypesView(viewsets.ViewSet):
Returns: Returns:
Response: A response containing a list of distinct activity types. Response: A response containing a list of distinct activity types.
""" """
types = Adventure.objects.filter(user_id=request.user.id).values_list('activity_types', flat=True).distinct() types = Location.objects.filter(user=request.user).values_list('tags', flat=True).distinct()
allTypes = [] allTypes = []

View file

@ -1,12 +1,10 @@
from rest_framework import viewsets, status from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response from rest_framework.response import Response
from django.db.models import Q from django.db.models import Q
from adventures.models import Transportation from adventures.models import Transportation
from adventures.serializers import TransportationSerializer from adventures.serializers import TransportationSerializer
from rest_framework.exceptions import PermissionDenied from rest_framework.exceptions import PermissionDenied
from adventures.permissions import IsOwnerOrSharedWithFullAccess from adventures.permissions import IsOwnerOrSharedWithFullAccess
from rest_framework.permissions import IsAuthenticated
class TransportationViewSet(viewsets.ModelViewSet): class TransportationViewSet(viewsets.ModelViewSet):
queryset = Transportation.objects.all() queryset = Transportation.objects.all()
@ -17,7 +15,7 @@ class TransportationViewSet(viewsets.ModelViewSet):
if not request.user.is_authenticated: if not request.user.is_authenticated:
return Response(status=status.HTTP_403_FORBIDDEN) return Response(status=status.HTTP_403_FORBIDDEN)
queryset = Transportation.objects.filter( queryset = Transportation.objects.filter(
Q(user_id=request.user.id) Q(user=request.user.id)
) )
serializer = self.get_serializer(queryset, many=True) serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data) return Response(serializer.data)
@ -25,13 +23,13 @@ class TransportationViewSet(viewsets.ModelViewSet):
def get_queryset(self): def get_queryset(self):
user = self.request.user user = self.request.user
if self.action == 'retrieve': if self.action == 'retrieve':
# For individual adventure retrieval, include public adventures, user's own adventures and shared adventures # For individual adventure retrieval, include public locations, user's own locations and shared locations
return Transportation.objects.filter( return Transportation.objects.filter(
Q(is_public=True) | Q(user_id=user.id) | Q(collection__shared_with=user.id) Q(is_public=True) | Q(user=user.id) | Q(collection__shared_with=user.id)
).distinct().order_by('-updated_at') ).distinct().order_by('-updated_at')
# For other actions, include user's own adventures and shared adventures # For other actions, include user's own locations and shared locations
return Transportation.objects.filter( return Transportation.objects.filter(
Q(user_id=user.id) | Q(collection__shared_with=user.id) Q(user=user.id) | Q(collection__shared_with=user.id)
).distinct().order_by('-updated_at') ).distinct().order_by('-updated_at')
def partial_update(self, request, *args, **kwargs): def partial_update(self, request, *args, **kwargs):
@ -48,11 +46,11 @@ class TransportationViewSet(viewsets.ModelViewSet):
if new_collection is not None and new_collection != instance.collection: if new_collection is not None and new_collection != instance.collection:
# Check if the user is the owner of the new collection # Check if the user is the owner of the new collection
if new_collection.user_id != user or instance.user_id != user: if new_collection.user != user or instance.user != user:
raise PermissionDenied("You do not have permission to use this collection.") raise PermissionDenied("You do not have permission to use this collection.")
elif new_collection is None: elif new_collection is None:
# Handle the case where the user is trying to set the collection to None # Handle the case where the user is trying to set the collection to None
if instance.collection is not None and instance.collection.user_id != user: if instance.collection is not None and instance.collection.user != user:
raise PermissionDenied("You cannot remove the collection as you are not the owner.") raise PermissionDenied("You cannot remove the collection as you are not the owner.")
# Perform the update # Perform the update
@ -73,12 +71,12 @@ class TransportationViewSet(viewsets.ModelViewSet):
if collection: if collection:
user = self.request.user user = self.request.user
# Check if the user is the owner or is in the shared_with list # Check if the user is the owner or is in the shared_with list
if collection.user_id != user and not collection.shared_with.filter(id=user.id).exists(): if collection.user != user and not collection.shared_with.filter(id=user.id).exists():
# Return an error response if the user does not have permission # Return an error response if the user does not have permission
raise PermissionDenied("You do not have permission to use this collection.") raise PermissionDenied("You do not have permission to use this collection.")
# if collection the owner of the adventure is the owner of the collection # if collection the owner of the adventure is the owner of the collection
serializer.save(user_id=collection.user_id) serializer.save(user=collection.user)
return return
# Save the adventure with the current user as the owner # Save the adventure with the current user as the owner
serializer.save(user_id=self.request.user) serializer.save(user=self.request.user)

View file

@ -8,7 +8,7 @@ from rest_framework.permissions import IsAuthenticated
import requests import requests
from rest_framework.pagination import PageNumberPagination from rest_framework.pagination import PageNumberPagination
from django.conf import settings from django.conf import settings
from adventures.models import AdventureImage from adventures.models import LocationImage
from django.http import HttpResponse from django.http import HttpResponse
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
import logging import logging
@ -253,11 +253,11 @@ class ImmichIntegrationView(viewsets.ViewSet):
""" """
GET an Immich image using the integration and asset ID. GET an Immich image using the integration and asset ID.
Access levels (in order of priority): Access levels (in order of priority):
1. Public adventures: accessible by anyone 1. Public locations: accessible by anyone
2. Private adventures in public collections: accessible by anyone 2. Private locations in public collections: accessible by anyone
3. Private adventures in private collections shared with user: accessible by shared users 3. Private locations in private collections shared with user: accessible by shared users
4. Private adventures: accessible only to the owner 4. Private locations: accessible only to the owner
5. No AdventureImage: owner can still view via integration 5. No LocationImage: owner can still view via integration
""" """
if not imageid or not integration_id: if not imageid or not integration_id:
return Response({ return Response({
@ -268,31 +268,31 @@ class ImmichIntegrationView(viewsets.ViewSet):
# Lookup integration and user # Lookup integration and user
integration = get_object_or_404(ImmichIntegration, id=integration_id) integration = get_object_or_404(ImmichIntegration, id=integration_id)
owner_id = integration.user_id owner_id = integration.user
# Try to find the image entry with collections and sharing information # Try to find the image entry with collections and sharing information
image_entry = ( image_entry = (
AdventureImage.objects LocationImage.objects
.filter(immich_id=imageid, user_id=owner_id) .filter(immich_id=imageid, user=owner_id)
.select_related('adventure') .select_related('location')
.prefetch_related('adventure__collections', 'adventure__collections__shared_with') .prefetch_related('location__collections', 'location__collections__shared_with')
.order_by('-adventure__is_public') # Public adventures first .order_by('-location__is_public') # Public locations first
.first() .first()
) )
# Access control # Access control
if image_entry: if image_entry:
adventure = image_entry.adventure location = image_entry.location
collections = adventure.collections.all() collections = location.collections.all()
# Determine access level # Determine access level
is_authorized = False is_authorized = False
# Level 1: Public adventure (highest priority) # Level 1: Public location (highest priority)
if adventure.is_public: if location.is_public:
is_authorized = True is_authorized = True
# Level 2: Private adventure in any public collection # Level 2: Private location in any public collection
elif any(collection.is_public for collection in collections): elif any(collection.is_public for collection in collections):
is_authorized = True is_authorized = True
@ -308,15 +308,15 @@ class ImmichIntegrationView(viewsets.ViewSet):
if not is_authorized: if not is_authorized:
return Response({ return Response({
'message': 'This image belongs to a private adventure and you are not authorized.', 'message': 'This image belongs to a private location and you are not authorized.',
'error': True, 'error': True,
'code': 'immich.permission_denied' 'code': 'immich.permission_denied'
}, status=status.HTTP_403_FORBIDDEN) }, status=status.HTTP_403_FORBIDDEN)
else: else:
# No AdventureImage exists; allow only the integration owner # No LocationImage exists; allow only the integration owner
if not request.user.is_authenticated or request.user.id != owner_id: if not request.user.is_authenticated or request.user.id != owner_id:
return Response({ return Response({
'message': 'Image is not linked to any adventure and you are not the owner.', 'message': 'Image is not linked to any location and you are not the owner.',
'error': True, 'error': True,
'code': 'immich.not_found' 'code': 'immich.not_found'
}, status=status.HTTP_404_NOT_FOUND) }, status=status.HTTP_404_NOT_FOUND)

View file

@ -295,6 +295,8 @@ CORS_ALLOWED_ORIGINS = [origin.strip() for origin in getenv('CSRF_TRUSTED_ORIGIN
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()]
CORS_ALLOW_CREDENTIALS = True
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
LOGGING = { LOGGING = {

View file

@ -6,5 +6,5 @@ def get_user_uuid(user):
class CustomModelSerializer(serializers.ModelSerializer): class CustomModelSerializer(serializers.ModelSerializer):
def to_representation(self, instance): def to_representation(self, instance):
representation = super().to_representation(instance) representation = super().to_representation(instance)
representation['user_id'] = get_user_uuid(instance.user_id) representation['user'] = get_user_uuid(instance.user)
return representation return representation

View file

@ -2,111 +2,199 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="AdventureLog Server" /> <meta name="description" content="AdventureLog API Server" />
<meta name="author" content="Sean Morley" /> <meta name="author" content="Sean Morley" />
<title>AdventureLog API Server</title> <title>AdventureLog API Server</title>
<!-- Latest compiled and minified CSS --> <!-- Bootstrap 5 CSS -->
<link <link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
rel="stylesheet"
/>
<!-- Bootstrap Icons -->
<link
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css"
rel="stylesheet" rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css"
/> />
<!-- Optional theme --> <style>
<link body {
rel="stylesheet" background-color: #f9f9fb;
href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css" color: #222;
/> font-family: "Segoe UI", sans-serif;
}
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries --> .navbar {
<!--[if lt IE 9]> background-color: #2c3e50;
<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script> }
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script> .navbar-brand,
<![endif]--> .nav-link {
color: #ecf0f1 !important;
}
.hero {
padding: 4rem 1rem;
background: linear-gradient(135deg, #2980b9, #6dd5fa);
color: white;
text-align: center;
border-radius: 0 0 1rem 1rem;
}
.hero h1 {
font-size: 3rem;
margin-bottom: 1rem;
}
.hero p {
font-size: 1.25rem;
margin-bottom: 2rem;
}
.api-response {
margin-top: 1rem;
font-family: monospace;
background-color: #eef2f7;
padding: 1rem;
border-radius: 0.5rem;
}
footer {
text-align: center;
padding: 2rem 0;
font-size: 0.9rem;
color: #888;
}
</style>
</head> </head>
<body>
<body role="document"> <!-- Navbar -->
<div class="navbar navbar-inverse" role="navigation"> <nav class="navbar navbar-expand-lg">
<div class="container"> <div class="container">
<div class="navbar-header"> <a class="navbar-brand" href="/">AdventureLog API</a>
<button <button
class="navbar-toggler"
type="button" type="button"
class="navbar-toggle collapsed" data-bs-toggle="collapse"
data-toggle="collapse" data-bs-target="#navbarNav"
data-target=".navbar-collapse"
> >
<span class="sr-only">Toggle navigation</span> <span class="navbar-toggler-icon"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button> </button>
<a class="navbar-brand" href="/">AdventureLog API Server</a> <div class="collapse navbar-collapse" id="navbarNav">
</div> <ul class="navbar-nav ms-auto">
<div class="collapse navbar-collapse"> <li class="nav-item"><a class="nav-link" href="/">Home</a></li>
<ul class="nav navbar-nav"> <li class="nav-item">
<li class="active"><a href="/">Server Home</a></li>
<li>
<a target="_blank" href="http://adventurelog.app"
>Documentation</a
>
</li>
<li>
<a <a
class="nav-link"
href="http://adventurelog.app"
target="_blank" target="_blank"
href="https://github.com/seanmorley15/AdventureLog"
>Source Code</a
> >
Documentation
</a>
</li>
<li class="nav-item">
<a
class="nav-link"
href="https://github.com/seanmorley15/AdventureLog"
target="_blank"
>
Source Code
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/docs">API Docs</a>
</li> </li>
<li><a href="/docs">API Docs</a></li>
</ul> </ul>
</div> </div>
<!--/.nav-collapse --> </div>
</nav>
<!-- Hero Section -->
<div class="hero">
<div class="container">
<h1><i class="bi bi-map"></i> AdventureLog API</h1>
<p>
The backend powering your travels — flexible, powerful, and open
source.
</p>
<a href="/docs" class="btn btn-light btn-lg shadow-sm"
><i class="bi bi-book"></i> Explore API Docs</a
>
</div> </div>
</div> </div>
<div class="container theme-showcase" role="main"> <!-- Main Content -->
{% block content %}{% endblock %} <div class="container my-5">
{% block content %}
<div class="text-center">
<h2>Try a Sample Request</h2>
<p>Use the form below to test an API POST request.</p>
<form
class="ajax-post d-flex flex-column align-items-center"
action="/api/test"
method="post"
style="max-width: 500px; margin: auto"
>
<input
type="text"
name="example"
placeholder="Enter example data"
class="form-control mb-3"
required
/>
<button type="submit" class="btn btn-primary">
<i class="bi bi-send"></i> Send Request
</button>
</form>
<div class="api-response"></div>
</div> </div>
<!-- Bootstrap core JavaScript {% endblock %}
================================================== --> </div>
<!-- Placed at the end of the document so the pages load faster -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script> <footer class="text-center text-muted py-4">
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script> Open source with ❤️ by
<script type="text/javascript"> <a href="https://seanmorley.com" target="_blank">Sean Morley</a> • View on
var error_response = function (data) { <a href="https://github.com/seanmorley15/AdventureLog" target="_blank"
>GitHub</a
>
<a href="https://adventurelog.app" target="_blank">adventurelog.app</a>
</footer>
<!-- Bootstrap JS -->
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
crossorigin="anonymous"
></script>
<!-- jQuery (optional, used here for legacy script) -->
<script
src="https://code.jquery.com/jquery-3.6.0.min.js"
integrity="sha384-vtXRMe3mGCbOeY7l30aIg8H9p3GdeSe4IFlP6G8JMa7o7lXvnz3GFKzPxzJdPfGK"
crossorigin="anonymous"
></script>
<script>
const error_response = (data) => {
$(".api-response").html( $(".api-response").html(
"API Response: " + `<strong>API Response:</strong> ${data.status} ${data.statusText}<br/><strong>Content:</strong> ${data.responseText}`
data.status +
" " +
data.statusText +
"<br/>Content: " +
data.responseText
); );
}; };
var susccess_response = function (data) { const susccess_response = (data) => {
$(".api-response").html( $(".api-response").html(
"API Response: OK<br/>Content: " + JSON.stringify(data) `<strong>API Response:</strong> OK<br/><strong>Content:</strong> ${JSON.stringify(
data,
null,
2
)}`
); );
}; };
$().ready(function () { $(document).ready(() => {
$("form.ajax-post button[type=submit]").click(function () { $("form.ajax-post button[type=submit]").click(function () {
var form = $("form.ajax-post"); const form = $("form.ajax-post");
$.post(form.attr("action"), form.serialize()) $.post(form.attr("action"), form.serialize())
.fail(function (data) { .fail(error_response)
error_response(data); .done(susccess_response);
})
.done(function (data) {
susccess_response(data);
});
return false; return false;
}); });
}); });
</script> </script>
{% block script %}{% endblock %} {% block script %}{% endblock %}
</body> </body>
</html> </html>

View file

@ -11,8 +11,8 @@ from django.shortcuts import get_object_or_404
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from .serializers import CustomUserDetailsSerializer as PublicUserSerializer from .serializers import CustomUserDetailsSerializer as PublicUserSerializer
from allauth.socialaccount.models import SocialApp from allauth.socialaccount.models import SocialApp
from adventures.serializers import AdventureSerializer, CollectionSerializer from adventures.serializers import LocationSerializer, CollectionSerializer
from adventures.models import Adventure, Collection from adventures.models import Location, Collection
from allauth.socialaccount.models import SocialAccount from allauth.socialaccount.models import SocialAccount
User = get_user_model() User = get_user_model()
@ -99,9 +99,9 @@ class PublicUserDetailView(APIView):
user.email = None user.email = None
# Get the users adventures and collections to include in the response # Get the users adventures and collections to include in the response
adventures = Adventure.objects.filter(user_id=user, is_public=True) adventures = Location.objects.filter(user=user, is_public=True)
collections = Collection.objects.filter(user_id=user, is_public=True) collections = Collection.objects.filter(user=user, is_public=True)
adventure_serializer = AdventureSerializer(adventures, many=True) adventure_serializer = LocationSerializer(adventures, many=True)
collection_serializer = CollectionSerializer(collections, many=True) collection_serializer = CollectionSerializer(collections, many=True)
return Response({ return Response({

View file

@ -1,12 +1,12 @@
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from adventures.models import Adventure from adventures.models import Location
import time import time
class Command(BaseCommand): class Command(BaseCommand):
help = 'Bulk geocode all adventures by triggering save on each one' help = 'Bulk geocode all adventures by triggering save on each one'
def handle(self, *args, **options): def handle(self, *args, **options):
adventures = Adventure.objects.all() adventures = Location.objects.all()
total = adventures.count() total = adventures.count()
self.stdout.write(self.style.SUCCESS(f'Starting bulk geocoding of {total} adventures')) self.stdout.write(self.style.SUCCESS(f'Starting bulk geocoding of {total} adventures'))

View file

@ -0,0 +1,18 @@
# Generated by Django 5.2.1 on 2025-06-19 20:24
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('worldtravel', '0016_remove_city_insert_id_remove_country_insert_id_and_more'),
]
operations = [
migrations.RenameField(
model_name='visitedregion',
old_name='user_id',
new_name='user',
),
]

View file

@ -0,0 +1,18 @@
# Generated by Django 5.2.1 on 2025-06-19 20:24
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('worldtravel', '0017_rename_user_id_visitedregion_user'),
]
operations = [
migrations.RenameField(
model_name='visitedcity',
old_name='user_id',
new_name='user',
),
]

View file

@ -6,7 +6,7 @@ from django.contrib.gis.db import models as gis_models
User = get_user_model() User = get_user_model()
default_user_id = 1 # Replace with an actual user ID default_user = 1 # Replace with an actual user ID
class Country(models.Model): class Country(models.Model):
@ -50,29 +50,29 @@ class City(models.Model):
class VisitedRegion(models.Model): class VisitedRegion(models.Model):
id = models.AutoField(primary_key=True) id = models.AutoField(primary_key=True)
user_id = models.ForeignKey( user = models.ForeignKey(
User, on_delete=models.CASCADE, default=default_user_id) User, on_delete=models.CASCADE, default=default_user)
region = models.ForeignKey(Region, on_delete=models.CASCADE) region = models.ForeignKey(Region, on_delete=models.CASCADE)
def __str__(self): def __str__(self):
return f'{self.region.name} ({self.region.country.country_code}) visited by: {self.user_id.username}' return f'{self.region.name} ({self.region.country.country_code}) visited by: {self.user.username}'
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if VisitedRegion.objects.filter(user_id=self.user_id, region=self.region).exists(): if VisitedRegion.objects.filter(user=self.user, region=self.region).exists():
raise ValidationError("Region already visited by user.") raise ValidationError("Region already visited by user.")
super().save(*args, **kwargs) super().save(*args, **kwargs)
class VisitedCity(models.Model): class VisitedCity(models.Model):
id = models.AutoField(primary_key=True) id = models.AutoField(primary_key=True)
user_id = models.ForeignKey( user = models.ForeignKey(
User, on_delete=models.CASCADE, default=default_user_id) User, on_delete=models.CASCADE, default=default_user)
city = models.ForeignKey(City, on_delete=models.CASCADE) city = models.ForeignKey(City, on_delete=models.CASCADE)
def __str__(self): def __str__(self):
return f'{self.city.name} ({self.city.region.name}) visited by: {self.user_id.username}' return f'{self.city.name} ({self.city.region.name}) visited by: {self.user.username}'
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if VisitedCity.objects.filter(user_id=self.user_id, city=self.city).exists(): if VisitedCity.objects.filter(user=self.user, city=self.city).exists():
raise ValidationError("City already visited by user.") raise ValidationError("City already visited by user.")
super().save(*args, **kwargs) super().save(*args, **kwargs)

View file

@ -25,7 +25,7 @@ class CountrySerializer(serializers.ModelSerializer):
user = getattr(request, 'user', None) user = getattr(request, 'user', None)
if user and user.is_authenticated: if user and user.is_authenticated:
return VisitedRegion.objects.filter(region__country=obj, user_id=user).count() return VisitedRegion.objects.filter(region__country=obj, user=user).count()
return 0 return 0
@ -62,8 +62,8 @@ class VisitedRegionSerializer(CustomModelSerializer):
class Meta: class Meta:
model = VisitedRegion model = VisitedRegion
fields = ['id', 'user_id', 'region', 'longitude', 'latitude', 'name'] fields = ['id', 'user', 'region', 'longitude', 'latitude', 'name']
read_only_fields = ['user_id', 'id', 'longitude', 'latitude', 'name'] read_only_fields = ['user', 'id', 'longitude', 'latitude', 'name']
class VisitedCitySerializer(CustomModelSerializer): class VisitedCitySerializer(CustomModelSerializer):
longitude = serializers.DecimalField(source='city.longitude', max_digits=9, decimal_places=6, read_only=True) longitude = serializers.DecimalField(source='city.longitude', max_digits=9, decimal_places=6, read_only=True)
@ -72,5 +72,5 @@ class VisitedCitySerializer(CustomModelSerializer):
class Meta: class Meta:
model = VisitedCity model = VisitedCity
fields = ['id', 'user_id', 'city', 'longitude', 'latitude', 'name'] fields = ['id', 'user', 'city', 'longitude', 'latitude', 'name']
read_only_fields = ['user_id', 'id', 'longitude', 'latitude', 'name'] read_only_fields = ['user', 'id', 'longitude', 'latitude', 'name']

View file

@ -13,7 +13,7 @@ from django.contrib.gis.geos import Point
from django.conf import settings from django.conf import settings
from rest_framework.decorators import action from rest_framework.decorators import action
from django.contrib.staticfiles import finders from django.contrib.staticfiles import finders
from adventures.models import Adventure from adventures.models import Location
@api_view(['GET']) @api_view(['GET'])
@permission_classes([IsAuthenticated]) @permission_classes([IsAuthenticated])
@ -28,7 +28,7 @@ def regions_by_country(request, country_code):
@permission_classes([IsAuthenticated]) @permission_classes([IsAuthenticated])
def visits_by_country(request, country_code): def visits_by_country(request, country_code):
country = get_object_or_404(Country, country_code=country_code) country = get_object_or_404(Country, country_code=country_code)
visits = VisitedRegion.objects.filter(region__country=country, user_id=request.user.id) visits = VisitedRegion.objects.filter(region__country=country, user=request.user.id)
serializer = VisitedRegionSerializer(visits, many=True) serializer = VisitedRegionSerializer(visits, many=True)
return Response(serializer.data) return Response(serializer.data)
@ -45,7 +45,7 @@ def cities_by_region(request, region_id):
@permission_classes([IsAuthenticated]) @permission_classes([IsAuthenticated])
def visits_by_region(request, region_id): def visits_by_region(request, region_id):
region = get_object_or_404(Region, id=region_id) region = get_object_or_404(Region, id=region_id)
visits = VisitedCity.objects.filter(city__region=region, user_id=request.user.id) visits = VisitedCity.objects.filter(city__region=region, user=request.user.id)
serializer = VisitedCitySerializer(visits, many=True) serializer = VisitedCitySerializer(visits, many=True)
return Response(serializer.data) return Response(serializer.data)
@ -71,7 +71,7 @@ class CountryViewSet(viewsets.ReadOnlyModelViewSet):
# 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 # 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']) @action(detail=False, methods=['post'])
def region_check_all_adventures(self, request): def region_check_all_adventures(self, request):
adventures = Adventure.objects.filter(user_id=request.user.id, type='visited') adventures = Location.objects.filter(user=request.user.id, type='visited')
count = 0 count = 0
for adventure in adventures: for adventure in adventures:
if adventure.latitude is not None and adventure.longitude is not None: if adventure.latitude is not None and adventure.longitude is not None:
@ -79,8 +79,8 @@ class CountryViewSet(viewsets.ReadOnlyModelViewSet):
point = Point(float(adventure.longitude), float(adventure.latitude), srid=4326) point = Point(float(adventure.longitude), float(adventure.latitude), srid=4326)
region = Region.objects.filter(geometry__contains=point).first() region = Region.objects.filter(geometry__contains=point).first()
if region: if region:
if not VisitedRegion.objects.filter(user_id=request.user.id, region=region).exists(): if not VisitedRegion.objects.filter(user=request.user.id, region=region).exists():
VisitedRegion.objects.create(user_id=request.user, region=region) VisitedRegion.objects.create(user=request.user, region=region)
count += 1 count += 1
except Exception as e: except Exception as e:
print(f"Error processing adventure {adventure.id}: {e}") print(f"Error processing adventure {adventure.id}: {e}")
@ -97,14 +97,14 @@ class VisitedRegionViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
def get_queryset(self): def get_queryset(self):
return VisitedRegion.objects.filter(user_id=self.request.user.id) return VisitedRegion.objects.filter(user=self.request.user.id)
def perform_create(self, serializer): def perform_create(self, serializer):
serializer.save(user_id=self.request.user) serializer.save(user=self.request.user)
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
request.data['user_id'] = request.user request.data['user'] = request.user
if VisitedRegion.objects.filter(user_id=request.user.id, region=request.data['region']).exists(): if VisitedRegion.objects.filter(user=request.user.id, region=request.data['region']).exists():
return Response({"error": "Region already visited by user."}, status=400) return Response({"error": "Region already visited by user."}, status=400)
serializer = self.get_serializer(data=request.data) serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
@ -115,7 +115,7 @@ class VisitedRegionViewSet(viewsets.ModelViewSet):
def destroy(self, request, **kwargs): def destroy(self, request, **kwargs):
# delete by region id # delete by region id
region = get_object_or_404(Region, id=kwargs['pk']) region = get_object_or_404(Region, id=kwargs['pk'])
visited_region = VisitedRegion.objects.filter(user_id=request.user.id, region=region) visited_region = VisitedRegion.objects.filter(user=request.user.id, region=region)
if visited_region.exists(): if visited_region.exists():
visited_region.delete() visited_region.delete()
return Response(status=status.HTTP_204_NO_CONTENT) return Response(status=status.HTTP_204_NO_CONTENT)
@ -127,27 +127,27 @@ class VisitedCityViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
def get_queryset(self): def get_queryset(self):
return VisitedCity.objects.filter(user_id=self.request.user.id) return VisitedCity.objects.filter(user=self.request.user.id)
def perform_create(self, serializer): def perform_create(self, serializer):
serializer.save(user_id=self.request.user) serializer.save(user=self.request.user)
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
request.data['user_id'] = request.user request.data['user'] = request.user
serializer = self.get_serializer(data=request.data) serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
self.perform_create(serializer) self.perform_create(serializer)
# if the region is not visited, visit it # if the region is not visited, visit it
region = serializer.validated_data['city'].region region = serializer.validated_data['city'].region
if not VisitedRegion.objects.filter(user_id=request.user.id, region=region).exists(): if not VisitedRegion.objects.filter(user=request.user.id, region=region).exists():
VisitedRegion.objects.create(user_id=request.user, region=region) VisitedRegion.objects.create(user=request.user, region=region)
headers = self.get_success_headers(serializer.data) headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def destroy(self, request, **kwargs): def destroy(self, request, **kwargs):
# delete by city id # delete by city id
city = get_object_or_404(City, id=kwargs['pk']) city = get_object_or_404(City, id=kwargs['pk'])
visited_city = VisitedCity.objects.filter(user_id=request.user.id, city=city) visited_city = VisitedCity.objects.filter(user=request.user.id, city=city)
if visited_city.exists(): if visited_city.exists():
visited_city.delete() visited_city.delete()
return Response(status=status.HTTP_204_NO_CONTENT) return Response(status=status.HTTP_204_NO_CONTENT)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 882 KiB

After

Width:  |  Height:  |  Size: 1.4 MiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 259 KiB

After

Width:  |  Height:  |  Size: 302 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 809 KiB

After

Width:  |  Height:  |  Size: 804 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 2 MiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 637 KiB

After

Width:  |  Height:  |  Size: 591 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 434 KiB

After

Width:  |  Height:  |  Size: 431 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 326 KiB

After

Width:  |  Height:  |  Size: 337 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 224 KiB

After

Width:  |  Height:  |  Size: 225 KiB

Before After
Before After

View file

@ -1,6 +1,6 @@
# About AdventureLog # About AdventureLog
Starting from a simple idea of tracking travel locations (called adventures), AdventureLog has grown into a full-fledged travel companion. With AdventureLog, you can log your adventures, keep track of where you've been on the world map, plan your next trip collaboratively, and share your experiences with friends and family. **AdventureLog is the ultimate travel companion for the modern-day explorer**. Starting from a simple idea of tracking travel locations, AdventureLog has grown into a full-fledged travel companion. With AdventureLog, you can log your adventures, keep track of where you've been on the world map, plan your next trip collaboratively, and share your experiences with friends and family. **AdventureLog is the ultimate travel companion for the modern-day explorer**.
## Features ## Features

View file

@ -4,18 +4,23 @@ Welcome to AdventureLog! This guide will help you get started with AdventureLog
## Key Terms ## Key Terms
#### Adventures #### Locations
- **Adventure**: think of an adventure as a point on a map, a location you want to visit, or a place you want to explore. An adventure can be anything you want it to be, from a local park to a famous landmark. ::: tip Terminology Update
- **Visit**: a visit is added to an adventure. It contains a date and notes about when the adventure was visited. If an adventure is visited multiple times, multiple visits can be added. If there are no visits on an adventure or the date of all visits is in the future, the adventure is considered planned. If the date of the visit is in the past, the adventure is considered completed. **Location has replaced Adventure:**
- **Category**: a category is a way to group adventures together. For example, you could have a category for parks, a category for museums, and a category for restaurants. The term "Location" is now used instead of "Adventure" - the usage remains the same, just the name has changed to better reflect the purpose of the feature.
- **Tag**: a tag is a way to add additional information to an adventure. For example, you could have a tag for the type of cuisine at a restaurant or the type of art at a museum. Multiple tags can be added to an adventure. :::
- **Image**: an image is a photo that is added to an adventure. Images can be added to an adventure to provide a visual representation of the location or to capture a memory of the visit. These can be uploaded from your device or with a service like [Immich](/docs/configuration/immich_integration) if the integration is enabled.
- **Attachment**: an attachment is a file that is added to an adventure. Attachments can be added to an adventure to provide additional information, such as a map of the location or a brochure from the visit. - **Location**: think of a location as a point on a map, a place you want to visit, have visited, or a place you want to explore. A location can be anything you want it to be, from a local park to a famous landmark. These are the building blocks of AdventureLog and are the fundamental unit of information in the app.
- **Visit**: a visit is added to an location. It contains a date and notes about when the location was visited. If a location is visited multiple times, multiple visits can be added. If there are no visits on a location or the date of all visits is in the future, the location is considered planned. If the date of the visit is in the past, the location is considered completed.
- **Category**: a category is a way to group locations together. For example, you could have a category for parks, a category for museums, and a category for restaurants.
- **Tag**: a tag is a way to add additional information to a location. For example, you could have a tag for the type of cuisine at a restaurant or the type of art at a museum. Multiple tags can be added to a location.
- **Image**: an image is a photo that is added to a location. Images can be added to a location to provide a visual representation or to capture a memory of the visit. These can be uploaded from your device or with a service like [Immich](/docs/configuration/immich_integration) if the integration is enabled.
- **Attachment**: an attachment is a file that is added to a location. Attachments can be added to a location to provide additional information, such as a map of the location or a brochure from the visit.
#### Collections #### Collections
- **Collection**: a collection is a way to group adventures together. Collections are flexible and can be used in many ways. When no start or end date is added to a collection, it acts like a folder to group adventures together. When a start and end date is added to a collection, it acts like a trip to group adventures together that were visited during that time period. With start and end dates, the collection is transformed into a full itinerary with a map showing the route taken between adventures. - **Collection**: a collection is a way to group locations together. Collections are flexible and can be used in many ways. When no start or end date is added to a collection, it acts like a folder to group locations together. When a start and end date is added to a collection, it acts like a trip to group locations together that were visited during that time period. With start and end dates, the collection is transformed into a full itinerary with a map showing the route taken between locations. For example, you could have a collection for a trip to Europe with dates so you can plan where you want to visit, a collection of local hiking trails, or a collection for a list of restaurants you want to try.
- **Transportation**: a transportation is a collection exclusive feature that allows you to add transportation information to your trip. This can be used to show the route taken between locations and the mode of transportation used. It can also be used to track flight information, such as flight number and departure time. - **Transportation**: a transportation is a collection exclusive feature that allows you to add transportation information to your trip. This can be used to show the route taken between locations and the mode of transportation used. It can also be used to track flight information, such as flight number and departure time.
- **Lodging**: a lodging is a collection exclusive feature that allows you to add lodging information to your trip. This can be used to plan where you will stay during your trip and add notes about the lodging location. It can also be used to track reservation information, such as reservation number and check-in time. - **Lodging**: a lodging is a collection exclusive feature that allows you to add lodging information to your trip. This can be used to plan where you will stay during your trip and add notes about the lodging location. It can also be used to track reservation information, such as reservation number and check-in time.
- **Note**: a note is a collection exclusive feature that allows you to add notes to your trip. This can be used to add additional information about your trip, such as a summary of the trip or a list of things to do. Notes can be assigned to a specific day of the trip to help organize the information. - **Note**: a note is a collection exclusive feature that allows you to add notes to your trip. This can be used to add additional information about your trip, such as a summary of the trip or a list of things to do. Notes can be assigned to a specific day of the trip to help organize the information.

View file

@ -31,9 +31,9 @@
section: 'main' section: 'main'
}, },
{ {
path: '/adventures', path: '/locations',
icon: MapMarker, icon: MapMarker,
label: 'navbar.my_adventures', label: 'locations.my_locations',
section: 'main' section: 'main'
}, },
{ {

View file

@ -1,9 +1,9 @@
<script lang="ts"> <script lang="ts">
import type { Adventure } from '$lib/types'; import type { Location } from '$lib/types';
import ImageDisplayModal from './ImageDisplayModal.svelte'; import ImageDisplayModal from './ImageDisplayModal.svelte';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
export let adventures: Adventure[] = []; export let adventures: Location[] = [];
let currentSlide = 0; let currentSlide = 0;
let image_url: string | null = null; let image_url: string | null = null;

View file

@ -10,8 +10,8 @@
display_name: '', display_name: '',
icon: '', icon: '',
id: '', id: '',
user_id: '', user: '',
num_adventures: 0 num_locations: 0
}; };
let isOpen: boolean = false; let isOpen: boolean = false;
@ -44,7 +44,7 @@
let dropdownRef: HTMLDivElement; let dropdownRef: HTMLDivElement;
onMount(() => { onMount(() => {
categories = categories.sort((a, b) => (b.num_adventures || 0) - (a.num_adventures || 0)); categories = categories.sort((a, b) => (b.num_locations || 0) - (a.num_locations || 0));
const handleClickOutside = (event: MouseEvent) => { const handleClickOutside = (event: MouseEvent) => {
if (dropdownRef && !dropdownRef.contains(event.target as Node)) { if (dropdownRef && !dropdownRef.contains(event.target as Node)) {
isOpen = false; isOpen = false;
@ -105,7 +105,7 @@
<!-- Sort the categories dynamically before rendering --> <!-- Sort the categories dynamically before rendering -->
{#each categories {#each categories
.slice() .slice()
.sort((a, b) => (b.num_adventures || 0) - (a.num_adventures || 0)) as category} .sort((a, b) => (b.num_locations || 0) - (a.num_locations || 0)) as category}
<button <button
type="button" type="button"
class="btn btn-neutral flex items-center space-x-2" class="btn btn-neutral flex items-center space-x-2"
@ -113,7 +113,7 @@
role="option" role="option"
aria-selected={selected_category && selected_category.id === category.id} aria-selected={selected_category && selected_category.id === category.id}
> >
<span>{category.display_name} {category.icon} ({category.num_adventures})</span> <span>{category.display_name} {category.icon} ({category.num_locations})</span>
</button> </button>
{/each} {/each}
</div> </div>

View file

@ -8,7 +8,7 @@
let adventure_types: Category[] = []; let adventure_types: Category[] = [];
onMount(async () => { onMount(async () => {
let categoryFetch = await fetch('/api/categories/categories'); let categoryFetch = await fetch('/api/categories');
let categoryData = await categoryFetch.json(); let categoryData = await categoryFetch.json();
adventure_types = categoryData; adventure_types = categoryData;
console.log(categoryData); console.log(categoryData);
@ -60,7 +60,7 @@
/> />
<span> <span>
{type.display_name} {type.display_name}
{type.icon} ({type.num_adventures}) {type.icon} ({type.num_locations})
</span> </span>
</label> </label>
</li> </li>

View file

@ -26,7 +26,7 @@
async function loadCategories() { async function loadCategories() {
try { try {
const res = await fetch('/api/categories/categories'); const res = await fetch('/api/categories');
if (res.ok) { if (res.ok) {
categories = await res.json(); categories = await res.json();
} }
@ -334,7 +334,7 @@
{#if isChanged} {#if isChanged}
<div class="alert alert-success mb-4"> <div class="alert alert-success mb-4">
<span>{$t('categories.update_after_refresh')}</span> <span>{$t('categories.location_update_after_refresh')}</span>
</div> </div>
{/if} {/if}

View file

@ -99,7 +99,7 @@
<Launch class="w-5 h-5" /> <Launch class="w-5 h-5" />
{$t('notes.open')} {$t('notes.open')}
</button> </button>
{#if checklist.user_id == user?.uuid || (collection && user && collection.shared_with?.includes(user.uuid))} {#if checklist.user == user?.uuid || (collection && user && collection.shared_with?.includes(user.uuid))}
<button <button
id="delete_adventure" id="delete_adventure"
data-umami-event="Delete Checklist" data-umami-event="Delete Checklist"

View file

@ -19,7 +19,7 @@
let warning: string | null = ''; let warning: string | null = '';
let isReadOnly = let isReadOnly =
!(checklist && user?.uuid == checklist?.user_id) && !(checklist && user?.uuid == checklist?.user) &&
!(user && collection && collection.shared_with && collection.shared_with.includes(user.uuid)) && !(user && collection && collection.shared_with && collection.shared_with.includes(user.uuid)) &&
!!checklist; !!checklist;
let newStatus: boolean = false; let newStatus: boolean = false;
@ -40,7 +40,7 @@
name: newItem, name: newItem,
is_checked: newStatus, is_checked: newStatus,
id: '', id: '',
user_id: '', user: '',
checklist: 0, checklist: 0,
created_at: '', created_at: '',
updated_at: '' updated_at: ''

View file

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import type { import type {
Adventure, Location,
Transportation, Transportation,
Lodging, Lodging,
Note, Note,
@ -24,14 +24,14 @@
import Filter from '~icons/mdi/filter-variant'; import Filter from '~icons/mdi/filter-variant';
// Component imports // Component imports
import AdventureCard from './AdventureCard.svelte'; import LocationCard from './LocationCard.svelte';
import TransportationCard from './TransportationCard.svelte'; import TransportationCard from './TransportationCard.svelte';
import LodgingCard from './LodgingCard.svelte'; import LodgingCard from './LodgingCard.svelte';
import NoteCard from './NoteCard.svelte'; import NoteCard from './NoteCard.svelte';
import ChecklistCard from './ChecklistCard.svelte'; import ChecklistCard from './ChecklistCard.svelte';
// Props // Props
export let adventures: Adventure[] = []; export let adventures: Location[] = [];
export let transportations: Transportation[] = []; export let transportations: Transportation[] = [];
export let lodging: Lodging[] = []; export let lodging: Lodging[] = [];
export let notes: Note[] = []; export let notes: Note[] = [];
@ -45,7 +45,7 @@
let sortOption: string = 'name_asc'; let sortOption: string = 'name_asc';
// Filtered arrays // Filtered arrays
let filteredAdventures: Adventure[] = []; let filteredAdventures: Location[] = [];
let filteredTransportations: Transportation[] = []; let filteredTransportations: Transportation[] = [];
let filteredLodging: Lodging[] = []; let filteredLodging: Lodging[] = [];
let filteredNotes: Note[] = []; let filteredNotes: Note[] = [];
@ -256,7 +256,7 @@
<div class="hidden md:flex items-center gap-2"> <div class="hidden md:flex items-center gap-2">
<div class="stats stats-horizontal bg-base-200/50 border border-base-300/50"> <div class="stats stats-horizontal bg-base-200/50 border border-base-300/50">
<div class="stat py-2 px-3"> <div class="stat py-2 px-3">
<div class="stat-title text-xs">{$t('navbar.adventures')}</div> <div class="stat-title text-xs">{$t('locations.locations')}</div>
<div class="stat-value text-sm text-info">{adventures.length}</div> <div class="stat-value text-sm text-info">{adventures.length}</div>
</div> </div>
<div class="stat py-2 px-3"> <div class="stat py-2 px-3">
@ -380,7 +380,7 @@
on:click={() => (filterOption = 'adventures')} on:click={() => (filterOption = 'adventures')}
> >
<Adventures class="w-3 h-3" /> <Adventures class="w-3 h-3" />
{$t('navbar.adventures')} {$t('locations.locations')}
</button> </button>
<button <button
class="tab tab-sm gap-2 {filterOption === 'transportation' class="tab tab-sm gap-2 {filterOption === 'transportation'
@ -432,7 +432,7 @@
</div> </div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 mx-4"> <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 mx-4">
{#each filteredAdventures as adventure} {#each filteredAdventures as adventure}
<AdventureCard <LocationCard
{user} {user}
on:edit={handleEditAdventure} on:edit={handleEditAdventure}
on:delete={handleDeleteAdventure} on:delete={handleDeleteAdventure}

View file

@ -9,7 +9,7 @@
import ShareVariant from '~icons/mdi/share-variant'; import ShareVariant from '~icons/mdi/share-variant';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import type { Adventure, Collection, User } from '$lib/types'; import type { Location, Collection, User } from '$lib/types';
import { addToast } from '$lib/toasts'; import { addToast } from '$lib/toasts';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
@ -91,7 +91,7 @@
> >
<!-- Image Carousel --> <!-- Image Carousel -->
<div class="relative overflow-hidden rounded-t-2xl"> <div class="relative overflow-hidden rounded-t-2xl">
<CardCarousel adventures={collection.adventures} /> <CardCarousel adventures={collection.locations} />
<!-- Badge Overlay --> <!-- Badge Overlay -->
<div class="absolute top-4 left-4 flex flex-col gap-2"> <div class="absolute top-4 left-4 flex flex-col gap-2">
@ -119,8 +119,8 @@
<!-- Adventure Count --> <!-- Adventure Count -->
<p class="text-sm text-base-content/70"> <p class="text-sm text-base-content/70">
{collection.adventures.length} {collection.locations.length}
{$t('navbar.adventures')} {$t('locations.locations')}
</p> </p>
<!-- Date Range --> <!-- Date Range -->
@ -170,7 +170,7 @@
<Launch class="w-4 h-4" /> <Launch class="w-4 h-4" />
{$t('adventures.open_details')} {$t('adventures.open_details')}
</button> </button>
{#if user && user.uuid == collection.user_id} {#if user && user.uuid == collection.user}
<div class="dropdown dropdown-end"> <div class="dropdown dropdown-end">
<button type="button" class="btn btn-square btn-sm btn-base-300"> <button type="button" class="btn btn-square btn-sm btn-base-300">
<DotsHorizontal class="w-5 h-5" /> <DotsHorizontal class="w-5 h-5" />

View file

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import type { Adventure, Collection } from '$lib/types'; import type { Location, Collection } from '$lib/types';
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
import { onMount } from 'svelte'; import { onMount } from 'svelte';
@ -172,7 +172,7 @@
</div> </div>
{#if searchQuery} {#if searchQuery}
<h3 class="text-xl font-semibold text-base-content/70 mb-2"> <h3 class="text-xl font-semibold text-base-content/70 mb-2">
{$t('adventures.no_collections_found')} {$t('adventures.no_collections_to_add_location')}
</h3> </h3>
<p class="text-base-content/50 text-center max-w-md mb-6"> <p class="text-base-content/50 text-center max-w-md mb-6">
{$t('collection.try_different_search')} {$t('collection.try_different_search')}
@ -183,7 +183,7 @@
</button> </button>
{:else} {:else}
<h3 class="text-xl font-semibold text-base-content/70 mb-2"> <h3 class="text-xl font-semibold text-base-content/70 mb-2">
{$t('adventures.no_collections_found')} {$t('adventures.no_collections_to_add_location')}
</h3> </h3>
<p class="text-base-content/50 text-center max-w-md"> <p class="text-base-content/50 text-center max-w-md">
{$t('adventures.create_collection_first')} {$t('adventures.create_collection_first')}

View file

@ -17,7 +17,7 @@
description: collectionToEdit?.description || '', description: collectionToEdit?.description || '',
start_date: collectionToEdit?.start_date || null, start_date: collectionToEdit?.start_date || null,
end_date: collectionToEdit?.end_date || null, end_date: collectionToEdit?.end_date || null,
user_id: collectionToEdit?.user_id || '', user: collectionToEdit?.user || '',
is_public: collectionToEdit?.is_public || false, is_public: collectionToEdit?.is_public || false,
adventures: collectionToEdit?.adventures || [], adventures: collectionToEdit?.adventures || [],
link: collectionToEdit?.link || '', link: collectionToEdit?.link || '',

View file

@ -3,10 +3,10 @@
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
import { onMount } from 'svelte'; import { onMount } from 'svelte';
let modal: HTMLDialogElement; let modal: HTMLDialogElement;
import type { Adventure } from '$lib/types'; import type { Location } from '$lib/types';
export let image: string; export let image: string;
export let adventure: Adventure | null = null; export let adventure: Location | null = null;
onMount(() => { onMount(() => {
modal = document.getElementById('my_modal_1') as HTMLDialogElement; modal = document.getElementById('my_modal_1') as HTMLDialogElement;

View file

@ -2,7 +2,7 @@
import { createEventDispatcher, onMount } from 'svelte'; import { createEventDispatcher, onMount } from 'svelte';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
import ImmichLogo from '$lib/assets/immich.svg'; import ImmichLogo from '$lib/assets/immich.svg';
import type { Adventure, ImmichAlbum } from '$lib/types'; import type { Location, ImmichAlbum } from '$lib/types';
import { debounce } from '$lib'; import { debounce } from '$lib';
let immichImages: any[] = []; let immichImages: any[] = [];
@ -12,7 +12,7 @@
let immichNextURL: string = ''; let immichNextURL: string = '';
let loading = false; let loading = false;
export let adventure: Adventure | null = null; export let adventure: Location | null = null;
export let copyImmichLocally: boolean = false; export let copyImmichLocally: boolean = false;
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
@ -21,7 +21,7 @@
let currentAlbum: string = ''; let currentAlbum: string = '';
let selectedDate: string = let selectedDate: string =
(adventure as Adventure | null)?.visits (adventure as Location | null)?.visits
.map((v) => new Date(v.end_date || v.start_date)) .map((v) => new Date(v.end_date || v.start_date))
.sort((a, b) => +b - +a)[0] .sort((a, b) => +b - +a)[0]
?.toISOString() ?.toISOString()

View file

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import type { Adventure, Collection, User } from '$lib/types'; import type { Location, Collection, User } from '$lib/types';
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
import Launch from '~icons/mdi/launch'; import Launch from '~icons/mdi/launch';
@ -31,19 +31,19 @@
let isCollectionModalOpen: boolean = false; let isCollectionModalOpen: boolean = false;
let isWarningModalOpen: boolean = false; let isWarningModalOpen: boolean = false;
export let adventure: Adventure; export let adventure: Location;
let displayActivityTypes: string[] = []; let displayActivityTypes: string[] = [];
let remainingCount = 0; let remainingCount = 0;
// Process activity types for display // Process activity types for display
$: { $: {
if (adventure.activity_types) { if (adventure.tags) {
if (adventure.activity_types.length <= 3) { if (adventure.tags.length <= 3) {
displayActivityTypes = adventure.activity_types; displayActivityTypes = adventure.tags;
remainingCount = 0; remainingCount = 0;
} else { } else {
displayActivityTypes = adventure.activity_types.slice(0, 3); displayActivityTypes = adventure.tags.slice(0, 3);
remainingCount = adventure.activity_types.length - 3; remainingCount = adventure.tags.length - 3;
} }
} }
} }
@ -77,11 +77,11 @@
} }
async function deleteAdventure() { async function deleteAdventure() {
let res = await fetch(`/api/adventures/${adventure.id}`, { let res = await fetch(`/api/locations/${adventure.id}`, {
method: 'DELETE' method: 'DELETE'
}); });
if (res.ok) { if (res.ok) {
addToast('info', $t('adventures.adventure_delete_success')); addToast('info', $t('adventures.location_delete_success'));
dispatch('delete', adventure.id); dispatch('delete', adventure.id);
} else { } else {
console.log('Error deleting adventure'); console.log('Error deleting adventure');
@ -98,7 +98,7 @@
updatedCollections.push(collectionId); updatedCollections.push(collectionId);
} }
let res = await fetch(`/api/adventures/${adventure.id}`, { let res = await fetch(`/api/locations/${adventure.id}`, {
method: 'PATCH', method: 'PATCH',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
@ -109,16 +109,16 @@
if (res.ok) { if (res.ok) {
// Only update the adventure.collections after server confirms success // Only update the adventure.collections after server confirms success
adventure.collections = updatedCollections; adventure.collections = updatedCollections;
addToast('info', `${$t('adventures.collection_link_success')}`); addToast('info', `${$t('adventures.collection_link_location_success')}`);
} else { } else {
addToast('error', `${$t('adventures.collection_link_error')}`); addToast('error', `${$t('adventures.collection_link_location_error')}`);
} }
} }
async function removeFromCollection(event: CustomEvent<string>) { async function removeFromCollection(event: CustomEvent<string>) {
let collectionId = event.detail; let collectionId = event.detail;
if (!collectionId) { if (!collectionId) {
addToast('error', `${$t('adventures.collection_remove_error')}`); addToast('error', `${$t('adventures.collection_remove_location_error')}`);
return; return;
} }
@ -128,7 +128,7 @@
(c) => String(c) !== String(collectionId) (c) => String(c) !== String(collectionId)
); );
let res = await fetch(`/api/adventures/${adventure.id}`, { let res = await fetch(`/api/locations/${adventure.id}`, {
method: 'PATCH', method: 'PATCH',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
@ -139,9 +139,9 @@
if (res.ok) { if (res.ok) {
// Only update adventure.collections after server confirms success // Only update adventure.collections after server confirms success
adventure.collections = updatedCollections; adventure.collections = updatedCollections;
addToast('info', `${$t('adventures.collection_remove_success')}`); addToast('info', `${$t('adventures.collection_remove_location_success')}`);
} else { } else {
addToast('error', `${$t('adventures.collection_remove_error')}`); addToast('error', `${$t('adventures.collection_remove_location_error')}`);
} }
} }
} }
@ -166,9 +166,9 @@
{#if isWarningModalOpen} {#if isWarningModalOpen}
<DeleteWarning <DeleteWarning
title={$t('adventures.delete_adventure')} title={$t('adventures.delete_location')}
button_text="Delete" button_text="Delete"
description={$t('adventures.adventure_delete_confirm')} description={$t('adventures.location_delete_confirm')}
is_warning={false} is_warning={false}
on:close={() => (isWarningModalOpen = false)} on:close={() => (isWarningModalOpen = false)}
on:confirm={deleteAdventure} on:confirm={deleteAdventure}
@ -228,7 +228,7 @@
<!-- Header Section --> <!-- Header Section -->
<div class="space-y-3"> <div class="space-y-3">
<button <button
on:click={() => goto(`/adventures/${adventure.id}`)} on:click={() => goto(`/locations/${adventure.id}`)}
class="text-xl font-bold text-left hover:text-primary transition-colors duration-200 line-clamp-2 group-hover:underline" class="text-xl font-bold text-left hover:text-primary transition-colors duration-200 line-clamp-2 group-hover:underline"
> >
{adventure.name} {adventure.name}
@ -274,13 +274,13 @@
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<button <button
class="btn btn-neutral btn-sm flex-1 mr-2" class="btn btn-neutral btn-sm flex-1 mr-2"
on:click={() => goto(`/adventures/${adventure.id}`)} on:click={() => goto(`/locations/${adventure.id}`)}
> >
<Launch class="w-4 h-4" /> <Launch class="w-4 h-4" />
{$t('adventures.open_details')} {$t('adventures.open_details')}
</button> </button>
{#if adventure.user_id == user?.uuid || (collection && user && collection.shared_with?.includes(user.uuid))} {#if (adventure.user && adventure.user.uuid == user?.uuid) || (collection && user && collection.shared_with?.includes(user.uuid))}
<div class="dropdown dropdown-end"> <div class="dropdown dropdown-end">
<div tabindex="0" role="button" class="btn btn-square btn-sm btn-base-300"> <div tabindex="0" role="button" class="btn btn-square btn-sm btn-base-300">
<DotsHorizontal class="w-5 h-5" /> <DotsHorizontal class="w-5 h-5" />
@ -293,11 +293,11 @@
<li> <li>
<button on:click={editAdventure} class="flex items-center gap-2"> <button on:click={editAdventure} class="flex items-center gap-2">
<FileDocumentEdit class="w-4 h-4" /> <FileDocumentEdit class="w-4 h-4" />
{$t('adventures.edit_adventure')} {$t('adventures.edit_location')}
</button> </button>
</li> </li>
{#if user?.uuid == adventure.user_id} {#if user?.uuid == adventure.user?.uuid}
<li> <li>
<button <button
on:click={() => (isCollectionModalOpen = true)} on:click={() => (isCollectionModalOpen = true)}

View file

@ -2,12 +2,12 @@
import { getBasemapUrl } from '$lib'; import { getBasemapUrl } from '$lib';
import { appVersion } from '$lib/config'; import { appVersion } from '$lib/config';
import { addToast } from '$lib/toasts'; import { addToast } from '$lib/toasts';
import type { Adventure, Lodging, GeocodeSearchResult, Point, ReverseGeocode } from '$lib/types'; import type { Location, Lodging, GeocodeSearchResult, Point, ReverseGeocode } from '$lib/types';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
import { DefaultMarker, MapEvents, MapLibre } from 'svelte-maplibre'; import { DefaultMarker, MapEvents, MapLibre } from 'svelte-maplibre';
export let item: Adventure | Lodging; export let item: Location | Lodging;
export let triggerMarkVisted: boolean = false; export let triggerMarkVisted: boolean = false;
export let initialLatLng: { lat: number; lng: number } | null = null; // Used to pass the location from the map selection to the modal export let initialLatLng: { lat: number; lng: number } | null = null; // Used to pass the location from the map selection to the modal
@ -366,7 +366,7 @@ it would also work to just use on:click on the MapLibre component itself. -->
{reverseGeocodePlace.city {reverseGeocodePlace.city
? reverseGeocodePlace.city + ', ' ? reverseGeocodePlace.city + ', '
: ''}{reverseGeocodePlace.region}, {reverseGeocodePlace.country} : ''}{reverseGeocodePlace.region}, {reverseGeocodePlace.country}
{$t('adventures.will_be_marked')} {$t('adventures.will_be_marked_location')}
</span> </span>
</div> </div>
{/if} {/if}

View file

@ -1,10 +1,10 @@
<script lang="ts"> <script lang="ts">
import type { Adventure, User } from '$lib/types'; import type { Location, User } from '$lib/types';
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import AdventureCard from './AdventureCard.svelte'; import LocationCard from './LocationCard.svelte';
let modal: HTMLDialogElement; let modal: HTMLDialogElement;
// Icons - following the worldtravel pattern // Icons - following the worldtravel pattern
@ -17,8 +17,8 @@
import Public from '~icons/mdi/earth'; import Public from '~icons/mdi/earth';
import Private from '~icons/mdi/lock'; import Private from '~icons/mdi/lock';
let adventures: Adventure[] = []; let adventures: Location[] = [];
let filteredAdventures: Adventure[] = []; let filteredAdventures: Location[] = [];
let searchQuery: string = ''; let searchQuery: string = '';
let filterOption: string = 'all'; let filterOption: string = 'all';
let isLoading: boolean = true; let isLoading: boolean = true;
@ -69,7 +69,7 @@
modal.showModal(); modal.showModal();
} }
let res = await fetch(`/api/adventures/all/?include_collections=true`, { let res = await fetch(`/api/locations/all/?include_collections=true`, {
method: 'GET' method: 'GET'
}); });
@ -77,7 +77,7 @@
// Filter out adventures that are already linked to the collections // Filter out adventures that are already linked to the collections
if (collectionId) { if (collectionId) {
adventures = newAdventures.filter((adventure: Adventure) => { adventures = newAdventures.filter((adventure: Location) => {
return !(adventure.collections ?? []).includes(collectionId); return !(adventure.collections ?? []).includes(collectionId);
}); });
} else { } else {
@ -91,7 +91,7 @@
dispatch('close'); dispatch('close');
} }
function add(event: CustomEvent<Adventure>) { function add(event: CustomEvent<Location>) {
adventures = adventures.filter((a) => a.id !== event.detail.id); adventures = adventures.filter((a) => a.id !== event.detail.id);
dispatch('add', event.detail); dispatch('add', event.detail);
} }
@ -134,7 +134,7 @@
{filteredAdventures.length} {filteredAdventures.length}
{$t('worldtravel.of')} {$t('worldtravel.of')}
{totalAdventures} {totalAdventures}
{$t('navbar.adventures')} {$t('locations.locations')}
</p> </p>
</div> </div>
</div> </div>
@ -252,7 +252,7 @@
</div> </div>
{#if searchQuery || filterOption !== 'all'} {#if searchQuery || filterOption !== 'all'}
<h3 class="text-xl font-semibold text-base-content/70 mb-2"> <h3 class="text-xl font-semibold text-base-content/70 mb-2">
{$t('adventures.no_adventures_found')} {$t('adventures.no_locations_found')}
</h3> </h3>
<p class="text-base-content/50 text-center max-w-md mb-6"> <p class="text-base-content/50 text-center max-w-md mb-6">
{$t('collection.try_different_search')} {$t('collection.try_different_search')}
@ -274,7 +274,7 @@
<!-- Adventures Grid --> <!-- Adventures Grid -->
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 mb-6 p-4"> <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 mb-6 p-4">
{#each filteredAdventures as adventure} {#each filteredAdventures as adventure}
<AdventureCard {user} type="link" {adventure} on:link={add} /> <LocationCard {user} type="link" {adventure} on:link={add} />
{/each} {/each}
</div> </div>
{/if} {/if}

View file

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { createEventDispatcher, onMount } from 'svelte'; import { createEventDispatcher, onMount } from 'svelte';
import type { Adventure, Attachment, Category, Collection } from '$lib/types'; import type { Location, Attachment, Category, Collection } from '$lib/types';
import { addToast } from '$lib/toasts'; import { addToast } from '$lib/toasts';
import { deserialize } from '$app/forms'; import { deserialize } from '$app/forms';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
@ -23,7 +23,6 @@
let images: { id: string; image: string; is_primary: boolean; immich_id: string | null }[] = []; let images: { id: string; image: string; is_primary: boolean; immich_id: string | null }[] = [];
let warningMessage: string = ''; let warningMessage: string = '';
let constrainDates: boolean = false;
let categories: Category[] = []; let categories: Category[] = [];
@ -97,62 +96,62 @@
let wikiError: string = ''; let wikiError: string = '';
let adventure: Adventure = { let location: Location = {
id: '', id: '',
name: '', name: '',
visits: [], visits: [],
link: null, link: null,
description: null, description: null,
activity_types: [], tags: [],
rating: NaN, rating: NaN,
is_public: false, is_public: false,
latitude: NaN, latitude: NaN,
longitude: NaN, longitude: NaN,
location: null, location: null,
images: [], images: [],
user_id: null, user: null,
category: { category: {
id: '', id: '',
name: '', name: '',
display_name: '', display_name: '',
icon: '', icon: '',
user_id: '' user: ''
}, },
attachments: [] attachments: []
}; };
export let adventureToEdit: Adventure | null = null; export let locationToEdit: Location | null = null;
adventure = { location = {
id: adventureToEdit?.id || '', id: locationToEdit?.id || '',
name: adventureToEdit?.name || '', name: locationToEdit?.name || '',
link: adventureToEdit?.link || null, link: locationToEdit?.link || null,
description: adventureToEdit?.description || null, description: locationToEdit?.description || null,
activity_types: adventureToEdit?.activity_types || [], tags: locationToEdit?.tags || [],
rating: adventureToEdit?.rating || NaN, rating: locationToEdit?.rating || NaN,
is_public: adventureToEdit?.is_public || false, is_public: locationToEdit?.is_public || false,
latitude: adventureToEdit?.latitude || NaN, latitude: locationToEdit?.latitude || NaN,
longitude: adventureToEdit?.longitude || NaN, longitude: locationToEdit?.longitude || NaN,
location: adventureToEdit?.location || null, location: locationToEdit?.location || null,
images: adventureToEdit?.images || [], images: locationToEdit?.images || [],
user_id: adventureToEdit?.user_id || null, user: locationToEdit?.user || null,
visits: adventureToEdit?.visits || [], visits: locationToEdit?.visits || [],
is_visited: adventureToEdit?.is_visited || false, is_visited: locationToEdit?.is_visited || false,
category: adventureToEdit?.category || { category: locationToEdit?.category || {
id: '', id: '',
name: '', name: '',
display_name: '', display_name: '',
icon: '', icon: '',
user_id: '' user: ''
}, },
attachments: adventureToEdit?.attachments || [] attachments: locationToEdit?.attachments || []
}; };
onMount(async () => { onMount(async () => {
modal = document.getElementById('my_modal_1') as HTMLDialogElement; modal = document.getElementById('my_modal_1') as HTMLDialogElement;
modal.showModal(); modal.showModal();
let categoryFetch = await fetch('/api/categories/categories'); let categoryFetch = await fetch('/api/categories');
if (categoryFetch.ok) { if (categoryFetch.ok) {
categories = await categoryFetch.json(); categories = await categoryFetch.json();
} else { } else {
@ -183,15 +182,15 @@
let isLoading: boolean = false; let isLoading: boolean = false;
images = adventure.images || []; images = location.images || [];
$: { $: {
if (!adventure.rating) { if (!location.rating) {
adventure.rating = NaN; location.rating = NaN;
} }
} }
function deleteAttachment(event: CustomEvent<string>) { function deleteAttachment(event: CustomEvent<string>) {
adventure.attachments = adventure.attachments.filter( location.attachments = location.attachments.filter(
(attachment) => attachment.id !== event.detail (attachment) => attachment.id !== event.detail
); );
} }
@ -210,7 +209,7 @@
}); });
if (res.ok) { if (res.ok) {
let newAttachment = (await res.json()) as Attachment; let newAttachment = (await res.json()) as Attachment;
adventure.attachments = adventure.attachments.map((attachment) => { location.attachments = location.attachments.map((attachment) => {
if (attachment.id === newAttachment.id) { if (attachment.id === newAttachment.id) {
return newAttachment; return newAttachment;
} }
@ -245,18 +244,18 @@
const formData = new FormData(); const formData = new FormData();
formData.append('file', file); formData.append('file', file);
formData.append('adventure', adventure.id); formData.append('location', location.id);
formData.append('name', attachmentName); formData.append('name', attachmentName);
try { try {
const res = await fetch('/adventures?/attachment', { const res = await fetch('/locations?/attachment', {
method: 'POST', method: 'POST',
body: formData body: formData
}); });
if (res.ok) { if (res.ok) {
const newData = deserialize(await res.text()) as { data: Attachment }; const newData = deserialize(await res.text()) as { data: Attachment };
adventure.attachments = [...adventure.attachments, newData.data]; location.attachments = [...location.attachments, newData.data];
addToast('success', $t('adventures.attachment_upload_success')); addToast('success', $t('adventures.attachment_upload_success'));
attachmentName = ''; attachmentName = '';
} else { } else {
@ -273,7 +272,7 @@
} }
} }
let imageSearch: string = adventure.name || ''; let imageSearch: string = location.name || '';
async function removeImage(id: string) { async function removeImage(id: string) {
let res = await fetch(`/api/images/${id}/image_delete`, { let res = await fetch(`/api/images/${id}/image_delete`, {
@ -281,7 +280,7 @@
}); });
if (res.status === 204) { if (res.status === 204) {
images = images.filter((image) => image.id !== id); images = images.filter((image) => image.id !== id);
adventure.images = images; location.images = images;
addToast('success', $t('adventures.image_removed_success')); addToast('success', $t('adventures.image_removed_success'));
} else { } else {
addToast('error', $t('adventures.image_removed_error')); addToast('error', $t('adventures.image_removed_error'));
@ -291,7 +290,7 @@
let isDetails: boolean = true; let isDetails: boolean = true;
function saveAndClose() { function saveAndClose() {
dispatch('save', adventure); dispatch('save', location);
close(); close();
} }
@ -308,7 +307,7 @@
} }
return image; return image;
}); });
adventure.images = images; location.images = images;
} else { } else {
console.error('Error in makePrimaryImage:', res); console.error('Error in makePrimaryImage:', res);
} }
@ -326,9 +325,9 @@
async function uploadImage(file: File) { async function uploadImage(file: File) {
let formData = new FormData(); let formData = new FormData();
formData.append('image', file); formData.append('image', file);
formData.append('adventure', adventure.id); formData.append('location', location.id);
let res = await fetch(`/adventures?/image`, { let res = await fetch(`/locations?/image`, {
method: 'POST', method: 'POST',
body: formData body: formData
}); });
@ -341,7 +340,7 @@
immich_id: null immich_id: null
}; };
images = [...images, newImage]; images = [...images, newImage];
adventure.images = images; location.images = images;
addToast('success', $t('adventures.image_upload_success')); addToast('success', $t('adventures.image_upload_success'));
} else { } else {
addToast('error', $t('adventures.image_upload_error')); addToast('error', $t('adventures.image_upload_error'));
@ -359,7 +358,7 @@
let file = new File([data], 'image.jpg', { type: 'image/jpeg' }); let file = new File([data], 'image.jpg', { type: 'image/jpeg' });
let formData = new FormData(); let formData = new FormData();
formData.append('image', file); formData.append('image', file);
formData.append('adventure', adventure.id); formData.append('adventure', location.id);
await uploadImage(file); await uploadImage(file);
url = ''; url = '';
@ -383,8 +382,8 @@
wikiImageError = ''; wikiImageError = '';
let formData = new FormData(); let formData = new FormData();
formData.append('image', file); formData.append('image', file);
formData.append('adventure', adventure.id); formData.append('location', location.id);
let res2 = await fetch(`/adventures?/image`, { let res2 = await fetch(`/locations?/image`, {
method: 'POST', method: 'POST',
body: formData body: formData
}); });
@ -397,7 +396,7 @@
immich_id: null immich_id: null
}; };
images = [...images, newImage]; images = [...images, newImage];
adventure.images = images; location.images = images;
addToast('success', $t('adventures.image_upload_success')); addToast('success', $t('adventures.image_upload_success'));
} else { } else {
addToast('error', $t('adventures.image_upload_error')); addToast('error', $t('adventures.image_upload_error'));
@ -417,10 +416,10 @@
} }
async function generateDesc() { async function generateDesc() {
let res = await fetch(`/api/generate/desc/?name=${adventure.name}`); let res = await fetch(`/api/generate/desc/?name=${location.name}`);
let data = await res.json(); let data = await res.json();
if (data.extract?.length > 0) { if (data.extract?.length > 0) {
adventure.description = data.extract; location.description = data.extract;
wikiError = ''; wikiError = '';
} else { } else {
wikiError = $t('adventures.no_description_found'); wikiError = $t('adventures.no_description_found');
@ -433,72 +432,72 @@
isLoading = true; isLoading = true;
// if category icon is empty, set it to the default icon // if category icon is empty, set it to the default icon
if (adventure.category?.icon == '' || adventure.category?.icon == null) { if (location.category?.icon == '' || location.category?.icon == null) {
if (adventure.category) { if (location.category) {
adventure.category.icon = '🌍'; location.category.icon = '🌍';
} }
} }
if (adventure.id === '') { if (location.id === '') {
if (adventure.category?.display_name == '') { if (location.category?.display_name == '') {
if (categories.some((category) => category.name === 'general')) { if (categories.some((category) => category.name === 'general')) {
adventure.category = categories.find( location.category = categories.find(
(category) => category.name === 'general' (category) => category.name === 'general'
) as Category; ) as Category;
} else { } else {
adventure.category = { location.category = {
id: '', id: '',
name: 'general', name: 'general',
display_name: 'General', display_name: 'General',
icon: '🌍', icon: '🌍',
user_id: '' user: ''
}; };
} }
} }
// add this collection to the adventure // add this collection to the adventure
if (collection && collection.id) { if (collection && collection.id) {
adventure.collections = [collection.id]; location.collections = [collection.id];
} }
let res = await fetch('/api/adventures', { let res = await fetch('/api/locations', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}, },
body: JSON.stringify(adventure) body: JSON.stringify(location)
}); });
let data = await res.json(); let data = await res.json();
if (data.id) { if (data.id) {
adventure = data as Adventure; location = data as Location;
isDetails = false; isDetails = false;
warningMessage = ''; warningMessage = '';
addToast('success', $t('adventures.adventure_created')); addToast('success', $t('adventures.location_created'));
} else { } else {
warningMessage = findFirstValue(data) as string; warningMessage = findFirstValue(data) as string;
console.error(data); console.error(data);
addToast('error', $t('adventures.adventure_create_error')); addToast('error', $t('adventures.location_create_error'));
} }
} else { } else {
let res = await fetch(`/api/adventures/${adventure.id}`, { let res = await fetch(`/api/locations/${location.id}`, {
method: 'PATCH', method: 'PATCH',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}, },
body: JSON.stringify(adventure) body: JSON.stringify(location)
}); });
let data = await res.json(); let data = await res.json();
if (data.id) { if (data.id) {
adventure = data as Adventure; location = data as Location;
isDetails = false; isDetails = false;
warningMessage = ''; warningMessage = '';
addToast('success', $t('adventures.adventure_updated')); addToast('success', $t('adventures.location_updated'));
} else { } else {
warningMessage = Object.values(data)[0] as string; warningMessage = Object.values(data)[0] as string;
addToast('error', $t('adventures.adventure_update_error')); addToast('error', $t('adventures.location_update_error'));
} }
} }
imageSearch = adventure.name; imageSearch = location.name;
isLoading = false; isLoading = false;
} }
</script> </script>
@ -509,9 +508,9 @@
<!-- svelte-ignore a11y-no-noninteractive-element-interactions --> <!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
<div class="modal-box w-11/12 max-w-3xl" role="dialog" on:keydown={handleKeydown} tabindex="0"> <div class="modal-box w-11/12 max-w-3xl" role="dialog" on:keydown={handleKeydown} tabindex="0">
<h3 class="font-bold text-2xl"> <h3 class="font-bold text-2xl">
{adventureToEdit ? $t('adventures.edit_adventure') : $t('adventures.new_adventure')} {locationToEdit ? $t('adventures.edit_location') : $t('adventures.new_location')}
</h3> </h3>
{#if adventure.id === '' || isDetails} {#if location.id === '' || isDetails}
<div class="modal-action items-center"> <div class="modal-action items-center">
<form method="post" style="width: 100%;" on:submit={handleSubmit}> <form method="post" style="width: 100%;" on:submit={handleSubmit}>
<!-- Grid layout for form fields --> <!-- Grid layout for form fields -->
@ -529,7 +528,7 @@
type="text" type="text"
id="name" id="name"
name="name" name="name"
bind:value={adventure.name} bind:value={location.name}
class="input input-bordered w-full" class="input input-bordered w-full"
required required
/> />
@ -539,7 +538,7 @@
>{$t('adventures.category')}<span class="text-red-500">*</span></label >{$t('adventures.category')}<span class="text-red-500">*</span></label
><br /> ><br />
<CategoryDropdown bind:categories bind:selected_category={adventure.category} /> <CategoryDropdown bind:categories bind:selected_category={location.category} />
</div> </div>
<div> <div>
<label for="rating">{$t('adventures.rating')}</label><br /> <label for="rating">{$t('adventures.rating')}</label><br />
@ -548,7 +547,7 @@
min="0" min="0"
max="5" max="5"
hidden hidden
bind:value={adventure.rating} bind:value={location.rating}
id="rating" id="rating"
name="rating" name="rating"
class="input input-bordered w-full max-w-xs mt-1" class="input input-bordered w-full max-w-xs mt-1"
@ -558,48 +557,48 @@
type="radio" type="radio"
name="rating-2" name="rating-2"
class="rating-hidden" class="rating-hidden"
checked={Number.isNaN(adventure.rating)} checked={Number.isNaN(location.rating)}
/> />
<input <input
type="radio" type="radio"
name="rating-2" name="rating-2"
class="mask mask-star-2 bg-orange-400" class="mask mask-star-2 bg-orange-400"
on:click={() => (adventure.rating = 1)} on:click={() => (location.rating = 1)}
checked={adventure.rating === 1} checked={location.rating === 1}
/> />
<input <input
type="radio" type="radio"
name="rating-2" name="rating-2"
class="mask mask-star-2 bg-orange-400" class="mask mask-star-2 bg-orange-400"
on:click={() => (adventure.rating = 2)} on:click={() => (location.rating = 2)}
checked={adventure.rating === 2} checked={location.rating === 2}
/> />
<input <input
type="radio" type="radio"
name="rating-2" name="rating-2"
class="mask mask-star-2 bg-orange-400" class="mask mask-star-2 bg-orange-400"
on:click={() => (adventure.rating = 3)} on:click={() => (location.rating = 3)}
checked={adventure.rating === 3} checked={location.rating === 3}
/> />
<input <input
type="radio" type="radio"
name="rating-2" name="rating-2"
class="mask mask-star-2 bg-orange-400" class="mask mask-star-2 bg-orange-400"
on:click={() => (adventure.rating = 4)} on:click={() => (location.rating = 4)}
checked={adventure.rating === 4} checked={location.rating === 4}
/> />
<input <input
type="radio" type="radio"
name="rating-2" name="rating-2"
class="mask mask-star-2 bg-orange-400" class="mask mask-star-2 bg-orange-400"
on:click={() => (adventure.rating = 5)} on:click={() => (location.rating = 5)}
checked={adventure.rating === 5} checked={location.rating === 5}
/> />
{#if adventure.rating} {#if location.rating}
<button <button
type="button" type="button"
class="btn btn-sm btn-error ml-2" class="btn btn-sm btn-error ml-2"
on:click={() => (adventure.rating = NaN)} on:click={() => (location.rating = NaN)}
> >
{$t('adventures.remove')} {$t('adventures.remove')}
</button> </button>
@ -613,16 +612,16 @@
type="text" type="text"
id="link" id="link"
name="link" name="link"
bind:value={adventure.link} bind:value={location.link}
class="input input-bordered w-full" class="input input-bordered w-full"
/> />
</div> </div>
</div> </div>
<div> <div>
<label for="description">{$t('adventures.description')}</label><br /> <label for="description">{$t('adventures.description')}</label><br />
<MarkdownEditor bind:text={adventure.description} /> <MarkdownEditor bind:text={location.description} />
<div class="mt-2"> <div class="mt-2">
<div class="tooltip tooltip-right" data-tip={$t('adventures.wiki_desc')}> <div class="tooltip tooltip-right" data-tip={$t('adventures.wiki_location_desc')}>
<button type="button" class="btn btn-neutral mt-2" on:click={generateDesc} <button type="button" class="btn btn-neutral mt-2" on:click={generateDesc}
>{$t('adventures.generate_desc')}</button >{$t('adventures.generate_desc')}</button
> >
@ -630,17 +629,17 @@
<p class="text-red-500">{wikiError}</p> <p class="text-red-500">{wikiError}</p>
</div> </div>
</div> </div>
{#if !adventureToEdit || (adventureToEdit.collections && adventureToEdit.collections.length === 0)} {#if !locationToEdit || (locationToEdit.collections && locationToEdit.collections.length === 0)}
<div> <div>
<div class="form-control flex items-start mt-1"> <div class="form-control flex items-start mt-1">
<label class="label cursor-pointer flex items-start space-x-2"> <label class="label cursor-pointer flex items-start space-x-2">
<span class="label-text">{$t('adventures.public_adventure')}</span> <span class="label-text">{$t('adventures.public_location')}</span>
<input <input
type="checkbox" type="checkbox"
class="toggle toggle-primary" class="toggle toggle-primary"
id="is_public" id="is_public"
name="is_public" name="is_public"
bind:checked={adventure.is_public} bind:checked={location.is_public}
/> />
</label> </label>
</div> </div>
@ -649,27 +648,27 @@
</div> </div>
</div> </div>
<LocationDropdown bind:item={adventure} bind:triggerMarkVisted {initialLatLng} /> <LocationDropdown bind:item={location} bind:triggerMarkVisted {initialLatLng} />
<div class="collapse collapse-plus bg-base-200 mb-4 overflow-visible"> <div class="collapse collapse-plus bg-base-200 mb-4 overflow-visible">
<input type="checkbox" /> <input type="checkbox" />
<div class="collapse-title text-xl font-medium"> <div class="collapse-title text-xl font-medium">
{$t('adventures.tags')} ({adventure.activity_types?.length || 0}) {$t('adventures.tags')} ({location.tags?.length || 0})
</div> </div>
<div class="collapse-content"> <div class="collapse-content">
<input <input
type="text" type="text"
id="activity_types" id="tags"
name="activity_types" name="tags"
hidden hidden
bind:value={adventure.activity_types} bind:value={location.tags}
class="input input-bordered w-full" class="input input-bordered w-full"
/> />
<ActivityComplete bind:activities={adventure.activity_types} /> <ActivityComplete bind:activities={location.tags} />
</div> </div>
</div> </div>
<DateRangeCollapse type="adventure" {collection} bind:visits={adventure.visits} /> <DateRangeCollapse type="adventure" {collection} bind:visits={location.visits} />
<div> <div>
<div class="mt-4"> <div class="mt-4">
@ -711,11 +710,11 @@
<div class="collapse collapse-plus bg-base-200 mb-4"> <div class="collapse collapse-plus bg-base-200 mb-4">
<input type="checkbox" /> <input type="checkbox" />
<div class="collapse-title text-xl font-medium"> <div class="collapse-title text-xl font-medium">
{$t('adventures.attachments')} ({adventure.attachments?.length || 0}) {$t('adventures.attachments')} ({location.attachments?.length || 0})
</div> </div>
<div class="collapse-content"> <div class="collapse-content">
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3"> <div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
{#each adventure.attachments as attachment} {#each location.attachments as attachment}
<AttachmentCard <AttachmentCard
{attachment} {attachment}
on:delete={deleteAttachment} on:delete={deleteAttachment}
@ -785,7 +784,7 @@
<div class="collapse collapse-plus bg-base-200 mb-4"> <div class="collapse collapse-plus bg-base-200 mb-4">
<input type="checkbox" checked /> <input type="checkbox" checked />
<div class="collapse-title text-xl font-medium"> <div class="collapse-title text-xl font-medium">
{$t('adventures.images')} ({adventure.images?.length || 0}) {$t('adventures.images')} ({location.images?.length || 0})
</div> </div>
<div class="collapse-content"> <div class="collapse-content">
<label for="image" class="block font-medium mb-2"> <label for="image" class="block font-medium mb-2">
@ -802,7 +801,7 @@
multiple multiple
on:change={handleMultipleFiles} on:change={handleMultipleFiles}
/> />
<input type="hidden" name="adventure" value={adventure.id} id="adventure" /> <input type="hidden" name="adventure" value={location.id} id="adventure" />
</form> </form>
<div class="mb-4"> <div class="mb-4">
@ -848,7 +847,7 @@
{#if immichIntegration} {#if immichIntegration}
<ImmichSelect <ImmichSelect
{adventure} adventure={location}
on:fetchImage={(e) => { on:fetchImage={(e) => {
url = e.detail; url = e.detail;
fetchImage(); fetchImage();
@ -862,7 +861,7 @@
immich_id: e.detail.immich_id immich_id: e.detail.immich_id
}; };
images = [...images, newImage]; images = [...images, newImage];
adventure.images = images; location.images = images;
addToast('success', $t('adventures.image_upload_success')); addToast('success', $t('adventures.image_upload_success'));
}} }}
/> />
@ -917,17 +916,17 @@
</div> </div>
{/if} {/if}
{#if adventure.is_public && adventure.id} {#if location.is_public && location.id}
<div class="bg-neutral p-4 mt-2 rounded-md shadow-sm text-neutral-content"> <div class="bg-neutral p-4 mt-2 rounded-md shadow-sm text-neutral-content">
<p class=" font-semibold">{$t('adventures.share_adventure')}</p> <p class=" font-semibold">{$t('adventures.share_location')}</p>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<p class="text-card-foreground font-mono"> <p class="text-card-foreground font-mono">
{window.location.origin}/adventures/{adventure.id} {window.location.origin}/locations/{location.id}
</p> </p>
<button <button
type="button" type="button"
on:click={() => { on:click={() => {
navigator.clipboard.writeText(`${window.location.origin}/adventures/${adventure.id}`); navigator.clipboard.writeText(`${window.location.origin}/locations/${location.id}`);
}} }}
class="inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 h-10 px-4 py-2" class="inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 h-10 px-4 py-2"
> >

View file

@ -156,7 +156,7 @@
</div> </div>
<!-- Reservation Info --> <!-- Reservation Info -->
{#if lodging.user_id == user?.uuid || (collection && user && collection.shared_with?.includes(user.uuid))} {#if lodging.user == user?.uuid || (collection && user && collection.shared_with?.includes(user.uuid))}
<div class="space-y-2"> <div class="space-y-2">
{#if lodging.reservation_number} {#if lodging.reservation_number}
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
@ -174,7 +174,7 @@
{/if} {/if}
<!-- Actions --> <!-- Actions -->
{#if lodging.user_id == user?.uuid || (collection && user && collection.shared_with?.includes(user.uuid))} {#if lodging.user == user?.uuid || (collection && user && collection.shared_with?.includes(user.uuid))}
<div class="pt-4 border-t border-base-300 flex justify-end gap-2"> <div class="pt-4 border-t border-base-300 flex justify-end gap-2">
<button <button
class="btn btn-neutral btn-sm flex items-center gap-1" class="btn btn-neutral btn-sm flex items-center gap-1"

View file

@ -31,7 +31,7 @@
function initializeLodging(lodgingToEdit: Lodging | null): Lodging { function initializeLodging(lodgingToEdit: Lodging | null): Lodging {
return { return {
id: lodgingToEdit?.id || '', id: lodgingToEdit?.id || '',
user_id: lodgingToEdit?.user_id || '', user: lodgingToEdit?.user || '',
name: lodgingToEdit?.name || '', name: lodgingToEdit?.name || '',
type: lodgingToEdit?.type || 'other', type: lodgingToEdit?.type || 'other',
description: lodgingToEdit?.description || '', description: lodgingToEdit?.description || '',

View file

@ -103,7 +103,7 @@
// Navigation items for better organization // Navigation items for better organization
const navigationItems = [ const navigationItems = [
{ path: '/adventures', icon: MapMarker, label: 'navbar.adventures' }, { path: '/locations', icon: MapMarker, label: 'locations.locations' },
{ path: '/collections', icon: FormatListBulletedSquare, label: 'navbar.collections' }, { path: '/collections', icon: FormatListBulletedSquare, label: 'navbar.collections' },
{ path: '/worldtravel', icon: Earth, label: 'navbar.worldtravel' }, { path: '/worldtravel', icon: Earth, label: 'navbar.worldtravel' },
{ path: '/map', icon: Map, label: 'navbar.map' }, { path: '/map', icon: Map, label: 'navbar.map' },

View file

@ -12,7 +12,7 @@
<img src={Lost} alt="Lost" class="w-1/2" /> <img src={Lost} alt="Lost" class="w-1/2" />
</div> </div>
<h1 class="mt-4 text-3xl font-bold tracking-tight text-foreground sm:text-4xl"> <h1 class="mt-4 text-3xl font-bold tracking-tight text-foreground sm:text-4xl">
{$t('adventures.no_adventures_found')} {$t('adventures.no_locations_found')}
</h1> </h1>
{#if !error} {#if !error}
<p class="mt-4 text-muted-foreground"> <p class="mt-4 text-muted-foreground">

View file

@ -124,7 +124,7 @@
<Launch class="w-5 h-5" /> <Launch class="w-5 h-5" />
{$t('notes.open')} {$t('notes.open')}
</button> </button>
{#if note.user_id == user?.uuid || (collection && user && collection.shared_with?.includes(user.uuid))} {#if note.user == user?.uuid || (collection && user && collection.shared_with?.includes(user.uuid))}
<button <button
id="delete_adventure" id="delete_adventure"
data-umami-event="Delete Adventure" data-umami-event="Delete Adventure"

View file

@ -20,7 +20,7 @@
let constrainDates: boolean = false; let constrainDates: boolean = false;
let isReadOnly = let isReadOnly =
!(note && user?.uuid == note?.user_id) && !(note && user?.uuid == note?.user) &&
!(user && collection && collection.shared_with && collection.shared_with.includes(user.uuid)) && !(user && collection && collection.shared_with && collection.shared_with.includes(user.uuid)) &&
!!note; !!note;

View file

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
// @ts-nocheck // @ts-nocheck
import type { Adventure, GeocodeSearchResult, Point } from '$lib/types'; import type { Location, GeocodeSearchResult, Point } from '$lib/types';
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
import { onMount } from 'svelte'; import { onMount } from 'svelte';
@ -13,7 +13,7 @@
let markers: Point[] = []; let markers: Point[] = [];
export let query: string | null = null; export let query: string | null = null;
export let adventure: Adventure; export let adventure: Location;
if (query) { if (query) {
geocode(); geocode();
@ -83,7 +83,7 @@
adventure.name = markers[0].name; adventure.name = markers[0].name;
} }
if (adventure.type == 'visited' || adventure.type == 'planned') { if (adventure.type == 'visited' || adventure.type == 'planned') {
adventure.activity_types = [...adventure.activity_types, markers[0].activity_type]; adventure.tags = [...adventure.tags, markers[0].activity_type];
} }
dispatch('submit', adventure); dispatch('submit', adventure);
close(); close();

View file

@ -192,7 +192,7 @@
</div> </div>
<!-- Actions --> <!-- Actions -->
{#if transportation.user_id === user?.uuid || (collection && user && collection.shared_with?.includes(user.uuid))} {#if transportation.user === user?.uuid || (collection && user && collection.shared_with?.includes(user.uuid))}
<div class="pt-4 border-t border-base-300 flex justify-end gap-2"> <div class="pt-4 border-t border-base-300 flex justify-end gap-2">
<button <button
class="btn btn-neutral btn-sm flex items-center gap-1" class="btn btn-neutral btn-sm flex items-center gap-1"

View file

@ -29,7 +29,7 @@
flight_number: transportationToEdit?.flight_number || '', flight_number: transportationToEdit?.flight_number || '',
from_location: transportationToEdit?.from_location || '', from_location: transportationToEdit?.from_location || '',
to_location: transportationToEdit?.to_location || '', to_location: transportationToEdit?.to_location || '',
user_id: transportationToEdit?.user_id || '', user: transportationToEdit?.user || '',
is_public: transportationToEdit?.is_public || false, is_public: transportationToEdit?.is_public || false,
collection: transportationToEdit?.collection || collection.id, collection: transportationToEdit?.collection || collection.id,
created_at: transportationToEdit?.created_at || '', created_at: transportationToEdit?.created_at || '',
@ -182,11 +182,11 @@
if (data.id) { if (data.id) {
transportation = data as Transportation; transportation = data as Transportation;
addToast('success', $t('adventures.adventure_created')); addToast('success', $t('adventures.location_created'));
dispatch('save', transportation); dispatch('save', transportation);
} else { } else {
console.error(data); console.error(data);
addToast('error', $t('adventures.adventure_create_error')); addToast('error', $t('adventures.location_create_error'));
} }
} else { } else {
let res = await fetch(`/api/transportations/${transportation.id}`, { let res = await fetch(`/api/transportations/${transportation.id}`, {
@ -200,10 +200,10 @@
if (data.id) { if (data.id) {
transportation = data as Transportation; transportation = data as Transportation;
addToast('success', $t('adventures.adventure_updated')); addToast('success', $t('adventures.location_updated'));
dispatch('save', transportation); dispatch('save', transportation);
} else { } else {
addToast('error', $t('adventures.adventure_update_error')); addToast('error', $t('adventures.location_update_error'));
} }
} }
} }

View file

@ -4,7 +4,7 @@ import randomBackgrounds from './json/backgrounds.json';
// @ts-ignore // @ts-ignore
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import type { import type {
Adventure, Location,
Background, Background,
Checklist, Checklist,
Collection, Collection,
@ -34,26 +34,6 @@ export function checkLink(link: string) {
} }
} }
export async function exportData() {
let res = await fetch('/api/adventures/all');
let adventures = (await res.json()) as Adventure[];
res = await fetch('/api/collections/all');
let collections = (await res.json()) as Collection[];
res = await fetch('/api/visitedregion');
let visitedRegions = await res.json();
const data = {
adventures,
collections,
visitedRegions
};
const blob = new Blob([JSON.stringify(data)], { type: 'application/json' });
return URL.createObjectURL(blob);
}
export function isValidUrl(url: string) { export function isValidUrl(url: string) {
try { try {
new URL(url); new URL(url);
@ -63,22 +43,22 @@ export function isValidUrl(url: string) {
} }
} }
export function groupAdventuresByDate( export function groupLocationsByDate(
adventures: Adventure[], locations: Location[],
startDate: Date, startDate: Date,
numberOfDays: number numberOfDays: number
): Record<string, Adventure[]> { ): Record<string, Location[]> {
const groupedAdventures: Record<string, Adventure[]> = {}; const groupedLocations: Record<string, Location[]> = {};
// Initialize all days in the range using DateTime // Initialize all days in the range using DateTime
for (let i = 0; i < numberOfDays; i++) { for (let i = 0; i < numberOfDays; i++) {
const currentDate = DateTime.fromJSDate(startDate).plus({ days: i }); const currentDate = DateTime.fromJSDate(startDate).plus({ days: i });
const dateString = currentDate.toISODate(); // 'YYYY-MM-DD' const dateString = currentDate.toISODate(); // 'YYYY-MM-DD'
groupedAdventures[dateString] = []; groupedLocations[dateString] = [];
} }
adventures.forEach((adventure) => { locations.forEach((location) => {
adventure.visits.forEach((visit) => { location.visits.forEach((visit: { start_date: string; end_date: string; timezone: any }) => {
if (visit.start_date) { if (visit.start_date) {
// Check if it's all-day: start has 00:00:00 AND (no end OR end also has 00:00:00) // Check if it's all-day: start has 00:00:00 AND (no end OR end also has 00:00:00)
const startHasZeros = isAllDay(visit.start_date); const startHasZeros = isAllDay(visit.start_date);
@ -115,10 +95,10 @@ export function groupAdventuresByDate(
const currentDate = DateTime.fromJSDate(startDate).plus({ days: i }); const currentDate = DateTime.fromJSDate(startDate).plus({ days: i });
const currentDateStr = currentDate.toISODate(); const currentDateStr = currentDate.toISODate();
// Include the current day if it falls within the adventure date range // Include the current day if it falls within the location date range
if (currentDateStr >= startDateStr && currentDateStr <= endDateStr) { if (currentDateStr >= startDateStr && currentDateStr <= endDateStr) {
if (groupedAdventures[currentDateStr]) { if (groupedLocations[currentDateStr]) {
groupedAdventures[currentDateStr].push(adventure); groupedLocations[currentDateStr].push(location);
} }
} }
} }
@ -126,7 +106,7 @@ export function groupAdventuresByDate(
}); });
}); });
return groupedAdventures; return groupedLocations;
} }
function getLocalDateString(date: Date): string { function getLocalDateString(date: Date): string {
@ -426,15 +406,6 @@ export let TRANSPORTATION_TYPES_ICONS = {
other: '❓' other: '❓'
}; };
export function getAdventureTypeLabel(type: string) {
// return the emoji ADVENTURE_TYPE_ICONS label for the given type if not found return ? emoji
if (type in ADVENTURE_TYPE_ICONS) {
return ADVENTURE_TYPE_ICONS[type as keyof typeof ADVENTURE_TYPE_ICONS];
} else {
return '❓';
}
}
export function getRandomBackground() { export function getRandomBackground() {
const today = new Date(); const today = new Date();

View file

@ -14,12 +14,11 @@ export type User = {
disable_password: boolean; disable_password: boolean;
}; };
export type Adventure = { export type Location = {
id: string; id: string;
user_id: string | null;
name: string; name: string;
location?: string | null; location?: string | null;
activity_types?: string[] | null; tags?: string[] | null;
description?: string | null; description?: string | null;
rating?: number | null; rating?: number | null;
link?: string | null; link?: string | null;
@ -45,13 +44,13 @@ export type Adventure = {
is_visited?: boolean; is_visited?: boolean;
category: Category | null; category: Category | null;
attachments: Attachment[]; attachments: Attachment[];
user?: User | null; user: User | null;
city?: City | null; city?: City | null;
region?: Region | null; region?: Region | null;
country?: Country | null; country?: Country | null;
}; };
export type AdditionalAdventure = Adventure & { export type AdditionalLocation = Location & {
sun_times: { sun_times: {
date: string; date: string;
visit_id: string; visit_id: string;
@ -96,7 +95,7 @@ export type City = {
export type VisitedRegion = { export type VisitedRegion = {
id: number; id: number;
region: string; region: string;
user_id: string; user: string;
longitude: number; longitude: number;
latitude: number; latitude: number;
name: string; name: string;
@ -105,7 +104,7 @@ export type VisitedRegion = {
export type VisitedCity = { export type VisitedCity = {
id: number; id: number;
city: string; city: string;
user_id: string; user: string;
longitude: number; longitude: number;
latitude: number; latitude: number;
name: string; name: string;
@ -123,11 +122,11 @@ export type Point = {
export type Collection = { export type Collection = {
id: string; id: string;
user_id: string; user: string;
name: string; name: string;
description: string; description: string;
is_public: boolean; is_public: boolean;
adventures: Adventure[]; locations: Location[];
created_at?: string | null; created_at?: string | null;
start_date: string | null; start_date: string | null;
end_date: string | null; end_date: string | null;
@ -153,7 +152,7 @@ export type GeocodeSearchResult = {
export type Transportation = { export type Transportation = {
id: string; id: string;
user_id: string; user: string;
type: string; type: string;
name: string; name: string;
description: string | null; description: string | null;
@ -179,7 +178,7 @@ export type Transportation = {
export type Note = { export type Note = {
id: string; id: string;
user_id: string; user: string;
name: string; name: string;
content: string | null; content: string | null;
links: string[] | null; links: string[] | null;
@ -192,7 +191,7 @@ export type Note = {
export type Checklist = { export type Checklist = {
id: string; id: string;
user_id: string; user: string;
name: string; name: string;
items: ChecklistItem[]; items: ChecklistItem[];
date: string | null; // ISO 8601 date string date: string | null; // ISO 8601 date string
@ -204,7 +203,7 @@ export type Checklist = {
export type ChecklistItem = { export type ChecklistItem = {
id: string; id: string;
user_id: string; user: string;
name: string; name: string;
is_checked: boolean; is_checked: boolean;
checklist: number; checklist: number;
@ -235,8 +234,8 @@ export type Category = {
name: string; name: string;
display_name: string; display_name: string;
icon: string; icon: string;
user_id: string; user: string;
num_adventures?: number | null; num_locations?: number | null;
}; };
export type ImmichIntegration = { export type ImmichIntegration = {
@ -277,15 +276,15 @@ export type ImmichAlbum = {
export type Attachment = { export type Attachment = {
id: string; id: string;
file: string; file: string;
adventure: string; location: string;
extension: string; extension: string;
user_id: string; user: string;
name: string; name: string;
}; };
export type Lodging = { export type Lodging = {
id: string; id: string;
user_id: string; user: string;
name: string; name: string;
type: string; type: string;
description: string | null; description: string | null;

View file

@ -14,18 +14,12 @@
"adventures": { "adventures": {
"activities": {}, "activities": {},
"add_to_collection": "Zur Sammlung hinzufügen", "add_to_collection": "Zur Sammlung hinzufügen",
"adventure_delete_confirm": "Sind Sie sicher, dass Sie dieses Abenteuer löschen möchten? \nDiese Aktion kann nicht rückgängig gemacht werden.",
"collection_link_error": "Fehler beim Verknüpfen des Abenteuers mit der Sammlung",
"collection_link_success": "Abenteuer erfolgreich mit Sammlung verknüpft!",
"collection_remove_error": "Beim Entfernen des Abenteuers aus der Sammlung ist ein Fehler aufgetreten",
"collection_remove_success": "Abenteuer erfolgreich aus der Sammlung entfernt!",
"delete": "Löschen", "delete": "Löschen",
"edit_adventure": "Abenteuer bearbeiten", "edit_adventure": "Abenteuer bearbeiten",
"no_image_found": "Kein Bild gefunden", "no_image_found": "Kein Bild gefunden",
"open_details": "Details", "open_details": "Details",
"remove_from_collection": "Aus Sammlung entfernen", "remove_from_collection": "Aus Sammlung entfernen",
"adventure": "Abenteuer", "adventure": "Abenteuer",
"adventure_delete_success": "Abenteuer erfolgreich gelöscht!",
"archive": "Archiv", "archive": "Archiv",
"archived": "Archiviert", "archived": "Archiviert",
"archived_collection_message": "Sammlung erfolgreich archiviert!", "archived_collection_message": "Sammlung erfolgreich archiviert!",
@ -39,7 +33,6 @@
"count_txt": "Suchergebnisse", "count_txt": "Suchergebnisse",
"date": "Datum", "date": "Datum",
"dates": "Termine", "dates": "Termine",
"delete_adventure": "Abenteuer löschen",
"delete_collection": "Sammlung löschen", "delete_collection": "Sammlung löschen",
"delete_collection_success": "Sammlung erfolgreich gelöscht!", "delete_collection_success": "Sammlung erfolgreich gelöscht!",
"descending": "Absteigend", "descending": "Absteigend",
@ -56,8 +49,6 @@
"my_collections": "Meine Sammlungen", "my_collections": "Meine Sammlungen",
"name": "Name", "name": "Name",
"no_image_url": "Unter dieser URL wurde kein Bild gefunden.", "no_image_url": "Unter dieser URL wurde kein Bild gefunden.",
"not_found": "Abenteuer nicht gefunden",
"not_found_desc": "Das von Ihnen gesuchte Abenteuer konnte nicht gefunden werden. \nBitte versuchen Sie ein anderes Abenteuer aus oder schauen Sie später noch mal vorbei.",
"open_filters": "Filter öffnen", "open_filters": "Filter öffnen",
"order_by": "Sortieren nach", "order_by": "Sortieren nach",
"order_direction": "Sortierreihenfolge", "order_direction": "Sortierreihenfolge",
@ -80,10 +71,6 @@
"activity_types": "Aktivitätstypen", "activity_types": "Aktivitätstypen",
"add": "Hinzufügen", "add": "Hinzufügen",
"add_notes": "Notizen hinzufügen", "add_notes": "Notizen hinzufügen",
"adventure_create_error": "Das Abenteuer konnte nicht erstellt werden",
"adventure_created": "Abenteuer erstellt",
"adventure_update_error": "Das Abenteuer konnte nicht aktualisiert werden",
"adventure_updated": "Abenteuer aktualisiert",
"basic_information": "Basisdaten", "basic_information": "Basisdaten",
"category": "Kategorie", "category": "Kategorie",
"clear_map": "Karte leeren", "clear_map": "Karte leeren",
@ -100,23 +87,19 @@
"location": "Standort", "location": "Standort",
"location_information": "Standortinformationen", "location_information": "Standortinformationen",
"my_images": "Meine Bilder", "my_images": "Meine Bilder",
"new_adventure": "Neues Abenteuer",
"no_description_found": "Keine Beschreibung gefunden", "no_description_found": "Keine Beschreibung gefunden",
"no_images": "Keine Bilder", "no_images": "Keine Bilder",
"no_location": "Bitte geben Sie einen Ort ein", "no_location": "Bitte geben Sie einen Ort ein",
"no_results": "Keine Ergebnisse gefunden", "no_results": "Keine Ergebnisse gefunden",
"public_adventure": "Öffentliches Abenteuer",
"remove": "Entfernen", "remove": "Entfernen",
"save_next": "Speichern & weiter", "save_next": "Speichern & weiter",
"search_for_location": "Nach einem Ort suchen", "search_for_location": "Nach einem Ort suchen",
"search_results": "Suchergebnisse", "search_results": "Suchergebnisse",
"see_adventures": "Siehe Abenteuer", "see_adventures": "Siehe Abenteuer",
"share_adventure": "Teilen Sie dieses Abenteuer!",
"start_date": "Startdatum", "start_date": "Startdatum",
"upload_image": "Bild hochladen", "upload_image": "Bild hochladen",
"url": "URL", "url": "URL",
"warning": "Warnung", "warning": "Warnung",
"wiki_desc": "Ruft einen Auszug aus einem Wikipedia-Artikel ab, der zum Namen des Abenteuers passt.",
"wikipedia": "Wikipedia", "wikipedia": "Wikipedia",
"adventure_not_found": "Keine Abenteuer vorhanden. \nFügen Sie welche über die Plus-Schaltfläche unten rechts hinzu oder versuchen Sie, die Filter zu ändern!", "adventure_not_found": "Keine Abenteuer vorhanden. \nFügen Sie welche über die Plus-Schaltfläche unten rechts hinzu oder versuchen Sie, die Filter zu ändern!",
"all": "Alle", "all": "Alle",
@ -124,7 +107,6 @@
"mark_visited": "als besucht markieren", "mark_visited": "als besucht markieren",
"my_adventures": "Meine Abenteuer", "my_adventures": "Meine Abenteuer",
"no_adventures_found": "Keine Abenteuer gefunden", "no_adventures_found": "Keine Abenteuer gefunden",
"no_collections_found": "Es wurden keine Sammlungen gefunden, die zu diesem Abenteuer hinzugefügt werden können.",
"no_linkable_adventures": "Es wurden keine Abenteuer gefunden, die mit dieser Sammlung verknüpft werden können.", "no_linkable_adventures": "Es wurden keine Abenteuer gefunden, die mit dieser Sammlung verknüpft werden können.",
"not_visited": "Nicht besucht", "not_visited": "Nicht besucht",
"regions_updated": "Regionen aktualisiert", "regions_updated": "Regionen aktualisiert",
@ -181,10 +163,7 @@
"starting_airport": "Startflughafen", "starting_airport": "Startflughafen",
"to": "Nach", "to": "Nach",
"transportation_delete_confirm": "Sind Sie sicher, dass Sie diesen Transport löschen möchten? \nDies lässt sich nicht rückgängig machen.", "transportation_delete_confirm": "Sind Sie sicher, dass Sie diesen Transport löschen möchten? \nDies lässt sich nicht rückgängig machen.",
"will_be_marked": "wird als besucht markiert, sobald das Abenteuer gespeichert wird.",
"cities_updated": "Städte aktualisiert", "cities_updated": "Städte aktualisiert",
"create_adventure": "Erstelle Abenteuer",
"no_adventures_to_recommendations": "Keine Abenteuer gefunden. \nFügen Sie mindestens ein Abenteuer hinzu, um Empfehlungen zu erhalten.",
"finding_recommendations": "Entdecken Sie verborgene Schätze für Ihr nächstes Abenteuer", "finding_recommendations": "Entdecken Sie verborgene Schätze für Ihr nächstes Abenteuer",
"attachment": "Anhang", "attachment": "Anhang",
"attachment_delete_success": "Anhang gelöscht!", "attachment_delete_success": "Anhang gelöscht!",
@ -246,7 +225,32 @@
"name_location": "Name, Ort", "name_location": "Name, Ort",
"collection_contents": "Sammelinhalt", "collection_contents": "Sammelinhalt",
"check_in": "Einchecken", "check_in": "Einchecken",
"check_out": "Kasse" "check_out": "Kasse",
"collection_link_location_error": "Fehlerverknüpfungsort zur Sammlung",
"collection_link_location_success": "Standort, die mit der Sammlung erfolgreich verknüpft sind!",
"collection_locations": "Sammelorte einbeziehen",
"collection_remove_location_error": "Fehler zur Entfernung des Standorts aus der Sammlung",
"collection_remove_location_success": "Standort erfolgreich aus der Sammlung entfernt!",
"create_location": "Standort erstellen",
"delete_location": "Position löschen",
"edit_location": "Standort bearbeiten",
"location_create_error": "Der Standort erstellt nicht",
"location_created": "Ort erstellt",
"location_delete_confirm": "Sind Sie sicher, dass Sie diesen Ort löschen möchten? \nDiese Aktion kann nicht rückgängig gemacht werden.",
"location_delete_success": "Standort erfolgreich gelöscht!",
"location_not_found": "Ort nicht gefunden",
"location_not_found_desc": "Der Ort, den Sie gesucht haben, konnte nicht gefunden werden. \nBitte probieren Sie einen anderen Ort aus oder schauen Sie später noch einmal vorbei.",
"location_update_error": "Der Standort nicht aktualisiert",
"location_updated": "Standort aktualisiert",
"new_location": "Neuer Standort",
"no_collections_to_add_location": "Keine Sammlungen, die diesen Ort hinzuzufügen.",
"no_locations_to_recommendations": "Keine Standorte gefunden. \nFügen Sie mindestens einen Ort hinzu, um Empfehlungen zu erhalten.",
"public_location": "Öffentliche Lage",
"share_location": "Teilen Sie diesen Ort!",
"visit_calendar": "Besuchen Sie den Kalender",
"wiki_location_desc": "Zieht Auszug aus dem Wikipedia -Artikel, der dem Namen des Standorts entspricht.",
"will_be_marked_location": "wird als besucht markiert, sobald der Standort gespeichert ist.",
"no_locations_found": "Keine Standorte gefunden"
}, },
"home": { "home": {
"desc_1": "Entdecken, planen und erkunden Sie mühelos", "desc_1": "Entdecken, planen und erkunden Sie mühelos",
@ -317,10 +321,10 @@
"public_tooltip": "Mit einem öffentlichen Profil können Benutzer Sammlungen mit Ihnen teilen und Ihr Profil auf der Benutzerseite anzeigen.", "public_tooltip": "Mit einem öffentlichen Profil können Benutzer Sammlungen mit Ihnen teilen und Ihr Profil auf der Benutzerseite anzeigen.",
"new_password": "Neues Passwort", "new_password": "Neues Passwort",
"or_3rd_party": "Oder melden Sie sich bei einem Drittanbieter an", "or_3rd_party": "Oder melden Sie sich bei einem Drittanbieter an",
"no_public_adventures": "Keine öffentlichen Abenteuer gefunden",
"no_public_collections": "Keine öffentlichen Sammlungen gefunden", "no_public_collections": "Keine öffentlichen Sammlungen gefunden",
"user_adventures": "Benutzerabenteuer", "user_collections": "Benutzersammlungen",
"user_collections": "Benutzersammlungen" "no_public_locations": "Keine öffentlichen Standorte gefunden",
"user_locations": "Benutzerstandorte"
}, },
"users": { "users": {
"no_users_found": "Keine Benutzer mit öffentlichem Profil gefunden." "no_users_found": "Keine Benutzer mit öffentlichem Profil gefunden."
@ -568,7 +572,13 @@
"search": { "search": {
"adventurelog_results": "AdventureLog-Ergebnisse", "adventurelog_results": "AdventureLog-Ergebnisse",
"online_results": "Online-Ergebnisse", "online_results": "Online-Ergebnisse",
"public_adventures": "Öffentliche Abenteuer" "public_adventures": "Öffentliche Abenteuer",
"cities": "Städte",
"countries": "Länder",
"found": "gefunden",
"result": "Ergebnis",
"results": "Ergebnisse",
"try_searching_desc": "Versuchen Sie, nach Abenteuern, Sammlungen, Ländern, Regionen, Städten oder Nutzern zu suchen."
}, },
"map": { "map": {
"add_adventure": "Neues Abenteuer hinzufügen", "add_adventure": "Neues Abenteuer hinzufügen",
@ -579,13 +589,16 @@
"show_visited_regions": "Besuchte Regionen anzeigen", "show_visited_regions": "Besuchte Regionen anzeigen",
"view_details": "Details anzeigen", "view_details": "Details anzeigen",
"adventure_stats": "Abenteuerstatistiken", "adventure_stats": "Abenteuerstatistiken",
"adventures_shown": "Abenteuer gezeigt",
"completion": "Fertigstellung", "completion": "Fertigstellung",
"display_options": "Anzeigenoptionen", "display_options": "Anzeigenoptionen",
"map_controls": "Kartensteuerungen", "map_controls": "Kartensteuerungen",
"marker_placed_on_map": "Marker auf der Karte platziert", "marker_placed_on_map": "Marker auf der Karte platziert",
"place_marker_desc": "Klicken Sie auf die Karte, um einen Marker zu platzieren, oder fügen Sie ein Abenteuer ohne Ort hinzu.", "regions": "Regionen",
"regions": "Regionen" "add_location": "Neuen Standort hinzufügen",
"add_location_at_marker": "Fügen Sie einen neuen Standort bei Marker hinzu",
"location_map": "Standortkarte",
"locations_shown": "Standorte gezeigt",
"place_marker_desc_location": "Klicken Sie auf die Karte, um einen Marker zu platzieren."
}, },
"languages": {}, "languages": {},
"share": { "share": {
@ -611,9 +624,9 @@
"no_shared_adventures": "Dieser Benutzer hat noch keine öffentlichen Abenteuer geteilt.", "no_shared_adventures": "Dieser Benutzer hat noch keine öffentlichen Abenteuer geteilt.",
"no_shared_collections": "Dieser Benutzer hat noch keine öffentlichen Sammlungen geteilt.", "no_shared_collections": "Dieser Benutzer hat noch keine öffentlichen Sammlungen geteilt.",
"planned_trips": "Geplante Reisen", "planned_trips": "Geplante Reisen",
"public_adventure_experiences": "Öffentliche Abenteuererlebnisse",
"travel_statistics": "Reisestatistik", "travel_statistics": "Reisestatistik",
"your_journey_at_a_glance": "Ihre Abenteuerreise auf einen Blick" "your_journey_at_a_glance": "Ihre Abenteuerreise auf einen Blick",
"public_location_experiences": "Öffentliche Standortlebnisse"
}, },
"categories": { "categories": {
"category_name": "Kategoriename", "category_name": "Kategoriename",
@ -622,9 +635,9 @@
"manage_categories": "Kategorien verwalten", "manage_categories": "Kategorien verwalten",
"no_categories_found": "Keine Kategorien gefunden.", "no_categories_found": "Keine Kategorien gefunden.",
"select_category": "Kategorie wählen", "select_category": "Kategorie wählen",
"update_after_refresh": "Die Abenteuerkarten werden aktualisiert, sobald Sie die Seite aktualisieren.",
"add_new_category": "Neue Kategorie hinzufügen", "add_new_category": "Neue Kategorie hinzufügen",
"name_required": "Der Kategorienname ist erforderlich" "name_required": "Der Kategorienname ist erforderlich",
"location_update_after_refresh": "Die Standortkarten werden aktualisiert, sobald Sie die Seite aktualisiert haben."
}, },
"dashboard": { "dashboard": {
"add_some": "Warum nicht gleich Ihr nächstes Abenteuer planen? Sie können ein neues Abenteuer hinzufügen, indem Sie auf den Button unten klicken.", "add_some": "Warum nicht gleich Ihr nächstes Abenteuer planen? Sie können ein neues Abenteuer hinzufügen, indem Sie auf den Button unten klicken.",
@ -670,9 +683,9 @@
"recomendations": { "recomendations": {
"recommendation": "Empfehlung", "recommendation": "Empfehlung",
"recommendations": "Empfehlungen", "recommendations": "Empfehlungen",
"adventure_recommendations": "Abenteuerempfehlungen",
"food": "Essen", "food": "Essen",
"tourism": "Tourismus" "tourism": "Tourismus",
"location_recommendations": "Standortempfehlungen"
}, },
"lodging": { "lodging": {
"apartment": "Wohnung", "apartment": "Wohnung",
@ -695,17 +708,19 @@
"google_maps_integration_desc": "Verbinden Sie Ihr Google Maps-Konto, um hochwertige Suchergebnisse und Empfehlungen für Standort zu erhalten." "google_maps_integration_desc": "Verbinden Sie Ihr Google Maps-Konto, um hochwertige Suchergebnisse und Empfehlungen für Standort zu erhalten."
}, },
"calendar": { "calendar": {
"all_categories": "Alle Kategorien",
"all_day_event": "Ganztägige Veranstaltung", "all_day_event": "Ganztägige Veranstaltung",
"calendar_overview": "Kalenderübersicht", "calendar_overview": "Kalenderübersicht",
"categories": "Kategorien",
"day": "Tag", "day": "Tag",
"events_scheduled": "Veranstaltungen geplant", "events_scheduled": "Veranstaltungen geplant",
"filter_by_category": "Filter nach Kategorie",
"filtered_results": "Gefilterte Ergebnisse", "filtered_results": "Gefilterte Ergebnisse",
"month": "Monat", "month": "Monat",
"today": "Heute", "today": "Heute",
"total_events": "Gesamtereignisse", "total_events": "Gesamtereignisse",
"week": "Woche" "week": "Woche"
},
"locations": {
"location": "Standort",
"locations": "Standorte",
"my_locations": "Meine Standorte"
} }
} }

View file

@ -64,9 +64,9 @@
"latest_travel_experiences": "Your latest travel experiences" "latest_travel_experiences": "Your latest travel experiences"
}, },
"adventures": { "adventures": {
"collection_remove_success": "Adventure removed from collection successfully!", "collection_remove_location_success": "Location removed from collection successfully!",
"collection_remove_error": "Error removing adventure from collection", "collection_remove_location_error": "Error removing location from collection",
"collection_link_success": "Adventure linked to collection successfully!", "collection_link_location_success": "Location linked to collection successfully!",
"invalid_date_range": "Invalid date range", "invalid_date_range": "Invalid date range",
"timezone": "Timezone", "timezone": "Timezone",
"no_visits": "No visits", "no_visits": "No visits",
@ -75,8 +75,8 @@
"departure_date": "Departure Date", "departure_date": "Departure Date",
"arrival_date": "Arrival Date", "arrival_date": "Arrival Date",
"no_image_found": "No image found", "no_image_found": "No image found",
"collection_link_error": "Error linking adventure to collection", "collection_link_location_error": "Error linking location to collection",
"adventure_delete_confirm": "Are you sure you want to delete this adventure? This action cannot be undone.", "location_delete_confirm": "Are you sure you want to delete this location? This action cannot be undone.",
"checklist_delete_confirm": "Are you sure you want to delete this checklist? This action cannot be undone.", "checklist_delete_confirm": "Are you sure you want to delete this checklist? This action cannot be undone.",
"note_delete_confirm": "Are you sure you want to delete this note? This action cannot be undone.", "note_delete_confirm": "Are you sure you want to delete this note? This action cannot be undone.",
"transportation_delete_confirm": "Are you sure you want to delete this transportation? This action cannot be undone.", "transportation_delete_confirm": "Are you sure you want to delete this transportation? This action cannot be undone.",
@ -87,11 +87,12 @@
"delete_lodging": "Delete Lodging", "delete_lodging": "Delete Lodging",
"open_details": "Open Details", "open_details": "Open Details",
"edit_adventure": "Edit Adventure", "edit_adventure": "Edit Adventure",
"edit_location": "Edit Location",
"remove_from_collection": "Remove from Collection", "remove_from_collection": "Remove from Collection",
"add_to_collection": "Add to Collection", "add_to_collection": "Add to Collection",
"delete": "Delete", "delete": "Delete",
"not_found": "Adventure not found", "location_not_found": "Location not found",
"not_found_desc": "The adventure you were looking for could not be found. Please try a different adventure or check back later.", "location_not_found_desc": "The location you were looking for could not be found. Please try a different location or check back later.",
"homepage": "Homepage", "homepage": "Homepage",
"collection": "Collection", "collection": "Collection",
"longitude": "Longitude", "longitude": "Longitude",
@ -122,7 +123,7 @@
"my_images": "My Images", "my_images": "My Images",
"no_images": "No Images", "no_images": "No Images",
"distance": "Distance", "distance": "Distance",
"share_adventure": "Share this Adventure!", "share_location": "Share this Location!",
"share_collection": "Share this Collection!", "share_collection": "Share this Collection!",
"copy_link": "Copy Link", "copy_link": "Copy Link",
"sun_times": "Sun Times", "sun_times": "Sun Times",
@ -149,18 +150,19 @@
"search_results": "Search results", "search_results": "Search results",
"collection_no_start_end_date": "Adding a start and end date to the collection will unlock itinerary planning features in the collection page.", "collection_no_start_end_date": "Adding a start and end date to the collection will unlock itinerary planning features in the collection page.",
"no_results": "No results found", "no_results": "No results found",
"wiki_desc": "Pulls excerpt from Wikipedia article matching the name of the adventure.", "wiki_location_desc": "Pulls excerpt from Wikipedia article matching the name of the location.",
"attachments": "Attachments", "attachments": "Attachments",
"attachment": "Attachment", "attachment": "Attachment",
"images": "Images", "images": "Images",
"generate_desc": "Generate Description", "generate_desc": "Generate Description",
"public_adventure": "Public Adventure", "public_location": "Public Location",
"location_information": "Location Information", "location_information": "Location Information",
"link": "Link", "link": "Link",
"links": "Links", "links": "Links",
"description": "Description", "description": "Description",
"sources": "Sources", "sources": "Sources",
"collection_adventures": "Include Collection Adventures", "collection_adventures": "Include Collection Adventures",
"collection_locations": "Include Collection Locations",
"filter": "Filter", "filter": "Filter",
"category_filter": "Category Filter", "category_filter": "Category Filter",
"category": "Category", "category": "Category",
@ -178,7 +180,7 @@
"edit_collection": "Edit Collection", "edit_collection": "Edit Collection",
"unarchive": "Unarchive", "unarchive": "Unarchive",
"archive": "Archive", "archive": "Archive",
"no_collections_found": "No collections found to add this adventure to.", "no_collections_to_add_location": "No collections found to add this location to.",
"create_collection_first": "Create a collection first to organize your adventures and memories.", "create_collection_first": "Create a collection first to organize your adventures and memories.",
"done": "Done", "done": "Done",
"adventures_available": "Adventures Available", "adventures_available": "Adventures Available",
@ -190,8 +192,8 @@
"cancel": "Cancel", "cancel": "Cancel",
"delete_collection_warning": "Are you sure you want to delete this collection? This action cannot be undone.", "delete_collection_warning": "Are you sure you want to delete this collection? This action cannot be undone.",
"delete_collection": "Delete Collection", "delete_collection": "Delete Collection",
"delete_adventure": "Delete Adventure", "delete_location": "Delete Location",
"adventure_delete_success": "Adventure deleted successfully!", "location_delete_success": "Location deleted successfully!",
"visited": "Visited", "visited": "Visited",
"planned": "Planned", "planned": "Planned",
"duration": "Duration", "duration": "Duration",
@ -209,21 +211,22 @@
"image_fetch_failed": "Failed to fetch image", "image_fetch_failed": "Failed to fetch image",
"no_location": "Please enter a location", "no_location": "Please enter a location",
"no_description_found": "No description found", "no_description_found": "No description found",
"adventure_created": "Adventure created", "location_created": "Location created",
"adventure_create_error": "Failed to create adventure", "location_create_error": "Failed to create location",
"lodging": "Lodging", "lodging": "Lodging",
"create_adventure": "Create Adventure", "create_location": "Create Location",
"adventure_updated": "Adventure updated", "location_updated": "Location updated",
"adventure_update_error": "Failed to update adventure", "location_update_error": "Failed to update location",
"set_to_pin": "Set to Pin", "set_to_pin": "Set to Pin",
"category_fetch_error": "Error fetching categories", "category_fetch_error": "Error fetching categories",
"new_adventure": "New Adventure", "new_location": "New Location",
"basic_information": "Basic Information", "basic_information": "Basic Information",
"no_adventures_to_recommendations": "No adventures found. Add at least one adventure to get recommendations.", "no_locations_to_recommendations": "No locations found. Add at least one location to get recommendations.",
"display_name": "Display Name", "display_name": "Display Name",
"adventure_not_found": "There are no adventures to display. Add some using the plus button at the bottom right or try changing filters!", "adventure_not_found": "There are no adventures to display. Add some using the plus button at the bottom right or try changing filters!",
"collection_contents": "Collection Contents", "collection_contents": "Collection Contents",
"no_adventures_found": "No adventures found", "no_adventures_found": "No adventures found",
"no_locations_found": "No locations found",
"no_adventures_message": "Start documenting your adventures and planning new ones. Every journey has a story worth telling.", "no_adventures_message": "Start documenting your adventures and planning new ones. Every journey has a story worth telling.",
"mark_visited": "Mark Visited", "mark_visited": "Mark Visited",
"error_updating_regions": "Error updating regions", "error_updating_regions": "Error updating regions",
@ -248,6 +251,7 @@
"checklists": "Checklists", "checklists": "Checklists",
"transportations": "Transportations", "transportations": "Transportations",
"adventure_calendar": "Adventure Calendar", "adventure_calendar": "Adventure Calendar",
"visit_calendar": "Visit Calendar",
"day": "Day", "day": "Day",
"itineary_by_date": "Itinerary by Date", "itineary_by_date": "Itinerary by Date",
"nothing_planned": "Nothing planned for this day. Enjoy the journey!", "nothing_planned": "Nothing planned for this day. Enjoy the journey!",
@ -263,7 +267,7 @@
"no_location_found": "No location found", "no_location_found": "No location found",
"from": "From", "from": "From",
"to": "To", "to": "To",
"will_be_marked": "will be marked as visited once the adventure is saved.", "will_be_marked_location": "will be marked as visited once the location is saved.",
"start": "Start", "start": "Start",
"end": "End", "end": "End",
"emoji_picker": "Emoji Picker", "emoji_picker": "Emoji Picker",
@ -373,9 +377,9 @@
"public_tooltip": "With a public profile, users can share collections with you and view your profile on the users page.", "public_tooltip": "With a public profile, users can share collections with you and view your profile on the users page.",
"new_password": "New Password (6+ characters)", "new_password": "New Password (6+ characters)",
"or_3rd_party": "Or login with a third-party service", "or_3rd_party": "Or login with a third-party service",
"no_public_adventures": "No public adventures found", "no_public_locations": "No public locations found",
"no_public_collections": "No public collections found", "no_public_collections": "No public collections found",
"user_adventures": "User Adventures", "user_locations": "User Locations",
"user_collections": "User Collections" "user_collections": "User Collections"
}, },
"users": { "users": {
@ -585,24 +589,33 @@
"search": { "search": {
"adventurelog_results": "AdventureLog Results", "adventurelog_results": "AdventureLog Results",
"public_adventures": "Public Adventures", "public_adventures": "Public Adventures",
"online_results": "Online Results" "online_results": "Online Results",
"result": "Result",
"results": "Results",
"found": "found",
"try_searching_desc": "Try searching for adventures, collections, countries, regions, cities, or users.",
"countries": "Countries",
"cities": "Cities"
}, },
"map": { "map": {
"view_details": "View Details", "view_details": "View Details",
"adventure_map": "Adventure Map", "adventure_map": "Adventure Map",
"location_map": "Location Map",
"map_options": "Map Options", "map_options": "Map Options",
"show_visited_regions": "Show Visited Regions", "show_visited_regions": "Show Visited Regions",
"add_adventure_at_marker": "Add New Adventure at Marker", "add_adventure_at_marker": "Add New Adventure at Marker",
"add_location_at_marker": "Add New Location at Marker",
"clear_marker": "Clear Marker", "clear_marker": "Clear Marker",
"add_adventure": "Add New Adventure", "add_adventure": "Add New Adventure",
"add_location": "Add New Location",
"adventure_stats": "Adventure Stats", "adventure_stats": "Adventure Stats",
"map_controls": "Map Controls", "map_controls": "Map Controls",
"regions": "Regions", "regions": "Regions",
"completion": "Completion", "completion": "Completion",
"display_options": "Display Options", "display_options": "Display Options",
"marker_placed_on_map": "Marker placed on map", "marker_placed_on_map": "Marker placed on map",
"place_marker_desc": "Click on the map to place a marker, or add an adventure without location.", "place_marker_desc_location": "Click on the map to place a marker.",
"adventures_shown": "adventures shown" "locations_shown": "locations shown"
}, },
"share": { "share": {
"shared": "Shared", "shared": "Shared",
@ -628,7 +641,7 @@
"planned_trips": "Planned trips", "planned_trips": "Planned trips",
"discovered": "discovered", "discovered": "discovered",
"explored": "explored", "explored": "explored",
"public_adventure_experiences": "Public adventure experiences", "public_location_experiences": "Public location experiences",
"no_shared_adventures": "This user hasn't shared any public adventures yet.", "no_shared_adventures": "This user hasn't shared any public adventures yet.",
"no_shared_collections": "This user hasn't shared any public collections yet." "no_shared_collections": "This user hasn't shared any public collections yet."
}, },
@ -637,7 +650,7 @@
"no_categories_found": "No categories found.", "no_categories_found": "No categories found.",
"edit_category": "Edit Category", "edit_category": "Edit Category",
"icon": "Icon", "icon": "Icon",
"update_after_refresh": "The adventure cards will be updated once you refresh the page.", "location_update_after_refresh": "The location cards will be updated once you refresh the page.",
"select_category": "Select Category", "select_category": "Select Category",
"category_name": "Category Name", "category_name": "Category Name",
"add_new_category": "Add New Category", "add_new_category": "Add New Category",
@ -690,7 +703,7 @@
"recomendations": { "recomendations": {
"recommendation": "Recommendation", "recommendation": "Recommendation",
"recommendations": "Recommendations", "recommendations": "Recommendations",
"adventure_recommendations": "Adventure Recommendations", "location_recommendations": "Location Recommendations",
"food": "Food", "food": "Food",
"tourism": "Tourism" "tourism": "Tourism"
}, },
@ -701,11 +714,13 @@
"day": "Day", "day": "Day",
"events_scheduled": "events scheduled", "events_scheduled": "events scheduled",
"total_events": "Total Events", "total_events": "Total Events",
"all_categories": "All Categories",
"calendar_overview": "Calendar Overview", "calendar_overview": "Calendar Overview",
"categories": "Categories",
"filtered_results": "Filtered Results", "filtered_results": "Filtered Results",
"filter_by_category": "Filter by Category",
"all_day_event": "All Day Event" "all_day_event": "All Day Event"
},
"locations": {
"location": "Location",
"locations": "Locations",
"my_locations": "My Locations"
} }
} }

View file

@ -64,11 +64,6 @@
"of_world": "del mundo" "of_world": "del mundo"
}, },
"adventures": { "adventures": {
"collection_remove_success": "¡Aventura eliminada de la colección con éxito!",
"collection_remove_error": "Error al eliminar la aventura de la colección",
"collection_link_success": "¡Aventura vinculada a la colección con éxito!",
"collection_link_error": "Error al vincular la aventura a la colección",
"adventure_delete_confirm": "¿Estás seguro de que quieres eliminar esta aventura? Esta acción no se puede deshacer.",
"open_details": "Abrir Detalles", "open_details": "Abrir Detalles",
"edit_adventure": "Editar Aventura", "edit_adventure": "Editar Aventura",
"remove_from_collection": "Eliminar de la Colección", "remove_from_collection": "Eliminar de la Colección",
@ -80,8 +75,6 @@
"homepage": "Página principal", "homepage": "Página principal",
"latitude": "Latitud", "latitude": "Latitud",
"longitude": "Longitud", "longitude": "Longitud",
"not_found": "Aventura no encontrada",
"not_found_desc": "La aventura que buscabas no se pudo encontrar. \nPruebe una aventura diferente o vuelva a consultar más tarde.",
"visit": "Visita", "visit": "Visita",
"visits": "Visitas", "visits": "Visitas",
"adventure": "Aventura", "adventure": "Aventura",
@ -116,8 +109,6 @@
"share": "Compartir", "share": "Compartir",
"unarchive": "Desarchivar", "unarchive": "Desarchivar",
"cancel": "Cancelar", "cancel": "Cancelar",
"adventure_delete_success": "¡Aventura eliminada con éxito!",
"delete_adventure": "Eliminar aventura",
"planned": "Planificado", "planned": "Planificado",
"visited": "Visitado", "visited": "Visitado",
"dates": "Fechas", "dates": "Fechas",
@ -134,10 +125,6 @@
"activity_types": "Tipos de actividad", "activity_types": "Tipos de actividad",
"add": "Agregar", "add": "Agregar",
"add_notes": "Agregar notas", "add_notes": "Agregar notas",
"adventure_create_error": "No se pudo crear la aventura",
"adventure_created": "Aventura creada",
"adventure_update_error": "No se pudo actualizar la aventura",
"adventure_updated": "Aventura actualizada",
"basic_information": "Información básica", "basic_information": "Información básica",
"category": "Categoría", "category": "Categoría",
"clear_map": "Limpiar mapa", "clear_map": "Limpiar mapa",
@ -153,26 +140,21 @@
"location": "Ubicación", "location": "Ubicación",
"location_information": "Información de ubicación", "location_information": "Información de ubicación",
"my_images": "Mis imágenes", "my_images": "Mis imágenes",
"new_adventure": "Nueva aventura",
"no_description_found": "No se encontró ninguna descripción", "no_description_found": "No se encontró ninguna descripción",
"no_images": "Sin imágenes", "no_images": "Sin imágenes",
"no_location": "Por favor ingresa una ubicación", "no_location": "Por favor ingresa una ubicación",
"no_results": "No se encontraron resultados", "no_results": "No se encontraron resultados",
"public_adventure": "Aventura pública",
"remove": "Eliminar", "remove": "Eliminar",
"save_next": "Guardar y Siguiente", "save_next": "Guardar y Siguiente",
"search_for_location": "Buscar una ubicación", "search_for_location": "Buscar una ubicación",
"search_results": "Resultados de búsqueda", "search_results": "Resultados de búsqueda",
"share_adventure": "¡Comparte esta aventura!",
"start_date": "Fecha de inicio", "start_date": "Fecha de inicio",
"upload_image": "Subir imagen", "upload_image": "Subir imagen",
"url": "URL", "url": "URL",
"warning": "Advertencia", "warning": "Advertencia",
"wiki_desc": "Extrae un extracto de un artículo de Wikipedia que coincide con el nombre de la aventura.",
"wikipedia": "Wikipedia", "wikipedia": "Wikipedia",
"adventure_not_found": "No hay aventuras que mostrar. \n¡Agregue algunas usando el botón más en la parte inferior derecha o intente cambiar los filtros!", "adventure_not_found": "No hay aventuras que mostrar. \n¡Agregue algunas usando el botón más en la parte inferior derecha o intente cambiar los filtros!",
"no_adventures_found": "No se encontraron aventuras", "no_adventures_found": "No se encontraron aventuras",
"no_collections_found": "No se encontraron colecciones para agregar esta aventura.",
"my_adventures": "Mis aventuras", "my_adventures": "Mis aventuras",
"no_linkable_adventures": "No se encontraron aventuras que puedan vincularse a esta colección.", "no_linkable_adventures": "No se encontraron aventuras que puedan vincularse a esta colección.",
"mark_visited": "Marcar como visitado", "mark_visited": "Marcar como visitado",
@ -233,10 +215,7 @@
"starting_airport": "Aeropuerto de inicio", "starting_airport": "Aeropuerto de inicio",
"to": "A", "to": "A",
"transportation_delete_confirm": "¿Está seguro de que desea eliminar este transporte? \nEsta acción no se puede deshacer.", "transportation_delete_confirm": "¿Está seguro de que desea eliminar este transporte? \nEsta acción no se puede deshacer.",
"will_be_marked": "se marcará como visitado una vez guardada la aventura.",
"cities_updated": "ciudades actualizadas", "cities_updated": "ciudades actualizadas",
"create_adventure": "Crear aventura",
"no_adventures_to_recommendations": "No se encontraron aventuras. \nAñade al menos una aventura para obtener recomendaciones.",
"finding_recommendations": "Descubriendo gemas ocultas para tu próxima aventura", "finding_recommendations": "Descubriendo gemas ocultas para tu próxima aventura",
"attachment": "Adjunto", "attachment": "Adjunto",
"attachment_delete_success": "¡El archivo adjunto se eliminó exitosamente!", "attachment_delete_success": "¡El archivo adjunto se eliminó exitosamente!",
@ -297,8 +276,33 @@
"loading_adventures": "Cargando aventuras ...", "loading_adventures": "Cargando aventuras ...",
"name_location": "Nombre, ubicación", "name_location": "Nombre, ubicación",
"collection_contents": "Contenido de la colección", "collection_contents": "Contenido de la colección",
"check_in": "Entrada", "check_in": "Registrarse",
"check_out": "Salida" "check_out": "Verificar",
"collection_link_location_error": "Error de vinculación de la ubicación para la recopilación",
"collection_link_location_success": "¡Ubicación vinculada a la colección con éxito!",
"collection_locations": "Incluir ubicaciones de colección",
"collection_remove_location_error": "Error de eliminación de la ubicación de la colección",
"collection_remove_location_success": "¡Ubicación eliminada de la colección con éxito!",
"create_location": "Crear ubicación",
"delete_location": "Eliminar la ubicación",
"edit_location": "Ubicación de edición",
"location_create_error": "No se pudo crear la ubicación",
"location_created": "Ubicación creada",
"location_delete_confirm": "¿Estás seguro de que quieres eliminar esta ubicación? \nEsta acción no se puede deshacer.",
"location_delete_success": "Ubicación eliminada con éxito!",
"location_not_found": "Ubicación no encontrada",
"location_not_found_desc": "No se podía encontrar la ubicación que estaba buscando. \nPruebe una ubicación diferente o vuelva a consultar más tarde.",
"location_update_error": "No se pudo actualizar la ubicación",
"location_updated": "Ubicación actualizada",
"new_location": "Nueva ubicación",
"no_collections_to_add_location": "No se encuentran colecciones para agregar esta ubicación a.",
"no_locations_to_recommendations": "No se encontraron ubicaciones. \nAgregue al menos una ubicación para obtener recomendaciones.",
"public_location": "Ubicación pública",
"share_location": "¡Comparte esta ubicación!",
"visit_calendar": "Visitar el calendario",
"wiki_location_desc": "Extrae extracto del artículo de Wikipedia que coincide con el nombre de la ubicación.",
"will_be_marked_location": "se marcará según lo visitado una vez que se guarde la ubicación.",
"no_locations_found": "No se encontraron ubicaciones"
}, },
"worldtravel": { "worldtravel": {
"all": "Todo", "all": "Todo",
@ -373,10 +377,10 @@
"public_tooltip": "Con un perfil público, los usuarios pueden compartir colecciones con usted y ver su perfil en la página de usuarios.", "public_tooltip": "Con un perfil público, los usuarios pueden compartir colecciones con usted y ver su perfil en la página de usuarios.",
"new_password": "Nueva contraseña", "new_password": "Nueva contraseña",
"or_3rd_party": "O inicie sesión con un servicio de terceros", "or_3rd_party": "O inicie sesión con un servicio de terceros",
"no_public_adventures": "No se encontraron aventuras públicas",
"no_public_collections": "No se encontraron colecciones públicas", "no_public_collections": "No se encontraron colecciones públicas",
"user_adventures": "Aventuras de usuario", "user_collections": "Colecciones de usuarios",
"user_collections": "Colecciones de usuarios" "no_public_locations": "No se encontraron ubicaciones públicas",
"user_locations": "Ubicación de usuarios"
}, },
"users": { "users": {
"no_users_found": "No se encontraron usuarios con perfiles públicos." "no_users_found": "No se encontraron usuarios con perfiles públicos."
@ -568,7 +572,13 @@
"search": { "search": {
"adventurelog_results": "Resultados del registro de aventuras", "adventurelog_results": "Resultados del registro de aventuras",
"online_results": "Resultados en línea", "online_results": "Resultados en línea",
"public_adventures": "Aventuras públicas" "public_adventures": "Aventuras públicas",
"cities": "Ciudades",
"countries": "Países",
"found": "encontró",
"result": "Resultado",
"results": "Resultados",
"try_searching_desc": "Intente buscar aventuras, colecciones, países, regiones, ciudades o usuarios."
}, },
"map": { "map": {
"add_adventure": "Agregar nueva aventura", "add_adventure": "Agregar nueva aventura",
@ -583,9 +593,12 @@
"display_options": "Opciones de visualización", "display_options": "Opciones de visualización",
"map_controls": "Controles de mapa", "map_controls": "Controles de mapa",
"marker_placed_on_map": "Marcador colocado en el mapa", "marker_placed_on_map": "Marcador colocado en el mapa",
"place_marker_desc": "Haga clic en el mapa para colocar un marcador o agregar una aventura sin ubicación.",
"regions": "Regiones", "regions": "Regiones",
"adventures_shown": "aventuras mostradas" "add_location": "Agregar nueva ubicación",
"add_location_at_marker": "Agregar nueva ubicación en el marcador",
"location_map": "Mapa de ubicación",
"locations_shown": "ubicaciones mostradas",
"place_marker_desc_location": "Haga clic en el mapa para colocar un marcador."
}, },
"share": { "share": {
"no_users_shared": "Ningún usuario compartió con", "no_users_shared": "Ningún usuario compartió con",
@ -611,9 +624,9 @@
"no_shared_adventures": "Este usuario aún no ha compartido ninguna aventura pública.", "no_shared_adventures": "Este usuario aún no ha compartido ninguna aventura pública.",
"no_shared_collections": "Este usuario aún no ha compartido ninguna colección pública.", "no_shared_collections": "Este usuario aún no ha compartido ninguna colección pública.",
"planned_trips": "Viajes planificados", "planned_trips": "Viajes planificados",
"public_adventure_experiences": "Experiencias de aventura pública",
"travel_statistics": "Estadísticas de viaje", "travel_statistics": "Estadísticas de viaje",
"your_journey_at_a_glance": "Tu viaje de aventura a un vistazo" "your_journey_at_a_glance": "Tu viaje de aventura a un vistazo",
"public_location_experiences": "Experiencias de ubicación pública"
}, },
"categories": { "categories": {
"category_name": "Nombre de categoría", "category_name": "Nombre de categoría",
@ -622,9 +635,9 @@
"manage_categories": "Administrar categorías", "manage_categories": "Administrar categorías",
"no_categories_found": "No se encontraron categorías.", "no_categories_found": "No se encontraron categorías.",
"select_category": "Seleccionar categoría", "select_category": "Seleccionar categoría",
"update_after_refresh": "Las tarjetas de aventuras se actualizarán una vez que actualices la página.",
"add_new_category": "Agregar nueva categoría", "add_new_category": "Agregar nueva categoría",
"name_required": "Se requiere el nombre de la categoría" "name_required": "Se requiere el nombre de la categoría",
"location_update_after_refresh": "Las tarjetas de ubicación se actualizarán una vez que actualice la página."
}, },
"dashboard": { "dashboard": {
"add_some": "¿Por qué no empezar a planificar tu próxima aventura? \nPuedes agregar una nueva aventura haciendo clic en el botón de abajo.", "add_some": "¿Por qué no empezar a planificar tu próxima aventura? \nPuedes agregar una nueva aventura haciendo clic en el botón de abajo.",
@ -670,9 +683,9 @@
"recomendations": { "recomendations": {
"recommendation": "Recomendación", "recommendation": "Recomendación",
"recommendations": "Recomendaciones", "recommendations": "Recomendaciones",
"adventure_recommendations": "Recomendaciones de aventura",
"food": "Comida", "food": "Comida",
"tourism": "Turismo" "tourism": "Turismo",
"location_recommendations": "Recomendaciones de ubicación"
}, },
"lodging": { "lodging": {
"apartment": "Apartamento", "apartment": "Apartamento",
@ -695,17 +708,19 @@
"google_maps_integration_desc": "Conecte su cuenta de Google Maps para obtener resultados y recomendaciones de búsqueda de ubicación de alta calidad." "google_maps_integration_desc": "Conecte su cuenta de Google Maps para obtener resultados y recomendaciones de búsqueda de ubicación de alta calidad."
}, },
"calendar": { "calendar": {
"all_categories": "Todas las categorías",
"all_day_event": "Evento todo el día", "all_day_event": "Evento todo el día",
"calendar_overview": "Descripción general del calendario", "calendar_overview": "Descripción general del calendario",
"categories": "Categorías",
"day": "Día", "day": "Día",
"events_scheduled": "Eventos programados", "events_scheduled": "Eventos programados",
"filter_by_category": "Filtrar por categoría",
"filtered_results": "Resultados filtrados", "filtered_results": "Resultados filtrados",
"month": "Mes", "month": "Mes",
"today": "Hoy", "today": "Hoy",
"total_events": "Total de eventos", "total_events": "Total de eventos",
"week": "Semana" "week": "Semana"
},
"locations": {
"location": "Ubicación",
"locations": "Ubicación",
"my_locations": "Mis ubicaciones"
} }
} }

View file

@ -14,18 +14,12 @@
"adventures": { "adventures": {
"activities": {}, "activities": {},
"add_to_collection": "Ajouter à la collection", "add_to_collection": "Ajouter à la collection",
"adventure_delete_confirm": "Êtes-vous sûr de vouloir supprimer cette aventure ? \nCette action ne peut pas être annulée.",
"collection_link_error": "Erreur lors de la liaison de l'aventure à la collection",
"collection_link_success": "Aventure liée à la collection avec succès !",
"collection_remove_error": "Erreur lors de la suppression de l'aventure de la collection",
"collection_remove_success": "Aventure supprimée de la collection avec succès !",
"delete": "Supprimer", "delete": "Supprimer",
"edit_adventure": "Modifier l'aventure", "edit_adventure": "Modifier l'aventure",
"no_image_found": "Aucune image trouvée", "no_image_found": "Aucune image trouvée",
"open_details": "Ouvrir les détails", "open_details": "Ouvrir les détails",
"remove_from_collection": "Supprimer de la collection", "remove_from_collection": "Supprimer de la collection",
"adventure": "Aventure", "adventure": "Aventure",
"adventure_delete_success": "Aventure supprimée avec succès !",
"archive": "Archiver", "archive": "Archiver",
"archived": "Archivée", "archived": "Archivée",
"archived_collection_message": "Collection archivée avec succès !", "archived_collection_message": "Collection archivée avec succès !",
@ -40,7 +34,6 @@
"create_new": "Créer une nouvelle aventure...", "create_new": "Créer une nouvelle aventure...",
"date": "Date", "date": "Date",
"dates": "Dates", "dates": "Dates",
"delete_adventure": "Supprimer l'aventure",
"delete_collection": "Supprimer la collection", "delete_collection": "Supprimer la collection",
"delete_collection_success": "Collection supprimée avec succès !", "delete_collection_success": "Collection supprimée avec succès !",
"descending": "Descendant", "descending": "Descendant",
@ -57,8 +50,6 @@
"my_collections": "Mes collections", "my_collections": "Mes collections",
"name": "Nom", "name": "Nom",
"no_image_url": "Aucune image trouvée à cette URL.", "no_image_url": "Aucune image trouvée à cette URL.",
"not_found": "Aventure introuvable",
"not_found_desc": "L'aventure que vous cherchez est introuvable. \nVeuillez essayer une autre aventure ou revenez plus tard.",
"open_filters": "Ouvrir les filtres", "open_filters": "Ouvrir les filtres",
"order_by": "Trier par", "order_by": "Trier par",
"order_direction": "Direction du tri", "order_direction": "Direction du tri",
@ -81,10 +72,6 @@
"activity_types": "Types d'activités", "activity_types": "Types d'activités",
"add": "Ajouter", "add": "Ajouter",
"add_notes": "Ajouter des notes", "add_notes": "Ajouter des notes",
"adventure_create_error": "Échec de la création de l'aventure",
"adventure_created": "Aventure créée",
"adventure_update_error": "Échec de la mise à jour de l'aventure",
"adventure_updated": "Aventure mise à jour",
"basic_information": "Informations de base", "basic_information": "Informations de base",
"category": "Catégorie", "category": "Catégorie",
"clear_map": "Effacer la carte", "clear_map": "Effacer la carte",
@ -100,23 +87,19 @@
"location": "Lieu", "location": "Lieu",
"location_information": "Informations de localisation", "location_information": "Informations de localisation",
"my_images": "Mes images", "my_images": "Mes images",
"new_adventure": "Nouvelle aventure",
"no_description_found": "Aucune description trouvée", "no_description_found": "Aucune description trouvée",
"no_images": "Aucune image", "no_images": "Aucune image",
"no_location": "Veuillez entrer un emplacement", "no_location": "Veuillez entrer un emplacement",
"no_results": "Aucun résultat trouvé", "no_results": "Aucun résultat trouvé",
"public_adventure": "Aventure publique",
"remove": "Retirer", "remove": "Retirer",
"save_next": "Sauvegarder", "save_next": "Sauvegarder",
"search_for_location": "Rechercher un lieu", "search_for_location": "Rechercher un lieu",
"search_results": "Résultats de la recherche", "search_results": "Résultats de la recherche",
"see_adventures": "Voir les aventures", "see_adventures": "Voir les aventures",
"share_adventure": "Partagez cette aventure !",
"start_date": "Date de début", "start_date": "Date de début",
"upload_image": "Télécharger une image", "upload_image": "Télécharger une image",
"url": "URL", "url": "URL",
"warning": "Avertissement", "warning": "Avertissement",
"wiki_desc": "Obtient un extrait de l'article Wikipédia correspondant au nom de l'aventure.",
"wikipedia": "Wikipédia", "wikipedia": "Wikipédia",
"adventure_not_found": "Il n'y a aucune aventure à afficher. \nAjoutez-en en utilisant le bouton '+' en bas à droite ou essayez de changer les filtres !", "adventure_not_found": "Il n'y a aucune aventure à afficher. \nAjoutez-en en utilisant le bouton '+' en bas à droite ou essayez de changer les filtres !",
"all": "Tous", "all": "Tous",
@ -124,7 +107,6 @@
"mark_visited": "Marquer comme visité", "mark_visited": "Marquer comme visité",
"my_adventures": "Mes aventures", "my_adventures": "Mes aventures",
"no_adventures_found": "Aucune aventure trouvée", "no_adventures_found": "Aucune aventure trouvée",
"no_collections_found": "Aucune collection trouvée pour ajouter cette aventure.",
"no_linkable_adventures": "Aucune aventure trouvée pouvant être liée à cette collection.", "no_linkable_adventures": "Aucune aventure trouvée pouvant être liée à cette collection.",
"not_visited": "Non visitée", "not_visited": "Non visitée",
"regions_updated": "régions mises à jour", "regions_updated": "régions mises à jour",
@ -181,10 +163,7 @@
"starting_airport": "Aéroport de départ", "starting_airport": "Aéroport de départ",
"to": "Vers", "to": "Vers",
"transportation_delete_confirm": "Etes-vous sûr de vouloir supprimer ce transport ? \nCette action ne peut pas être annulée.", "transportation_delete_confirm": "Etes-vous sûr de vouloir supprimer ce transport ? \nCette action ne peut pas être annulée.",
"will_be_marked": "sera marqué comme visité une fois laventure sauvegardée.",
"cities_updated": "villes mises à jour", "cities_updated": "villes mises à jour",
"create_adventure": "Créer une aventure",
"no_adventures_to_recommendations": "Aucune aventure trouvée. \nAjoutez au moins une aventure pour obtenir des recommandations.",
"finding_recommendations": "Découvrir des trésors cachés pour votre prochaine aventure", "finding_recommendations": "Découvrir des trésors cachés pour votre prochaine aventure",
"attachment": "Pièce jointe", "attachment": "Pièce jointe",
"attachment_delete_success": "Pièce jointe supprimée avec succès !", "attachment_delete_success": "Pièce jointe supprimée avec succès !",
@ -246,7 +225,32 @@
"name_location": "nom, emplacement", "name_location": "nom, emplacement",
"collection_contents": "Contenu de la collection", "collection_contents": "Contenu de la collection",
"check_in": "Enregistrement", "check_in": "Enregistrement",
"check_out": "Vérifier" "check_out": "Vérifier",
"collection_link_location_error": "Erreur liant l'emplacement à la collection",
"collection_link_location_success": "Emplacement lié à la collection avec succès!",
"collection_locations": "Inclure les emplacements de collecte",
"collection_remove_location_error": "Erreur de suppression de l'emplacement de la collection",
"collection_remove_location_success": "Emplacement retiré de la collection avec succès!",
"create_location": "Créer un emplacement",
"delete_location": "Supprimer l'emplacement",
"edit_location": "Modifier l'emplacement",
"location_create_error": "Échec de la création de l'emplacement",
"location_created": "Emplacement créé",
"location_delete_confirm": "Êtes-vous sûr de vouloir supprimer cet emplacement? \nCette action ne peut pas être annulée.",
"location_delete_success": "Emplacement supprimé avec succès!",
"location_not_found": "Emplacement introuvable",
"location_not_found_desc": "L'emplacement que vous recherchiez n'a pas pu être trouvé. \nVeuillez essayer un autre emplacement ou revenir plus tard.",
"location_update_error": "Échec de la mise à jour de l'emplacement",
"location_updated": "Emplacement mis à jour",
"new_location": "Nouvel emplacement",
"no_collections_to_add_location": "Aucune collection n'a été trouvée pour ajouter cet emplacement à.",
"no_locations_to_recommendations": "Aucun emplacement trouvé. \nAjoutez au moins un emplacement pour obtenir des recommandations.",
"public_location": "Lieu public",
"share_location": "Partagez cet emplacement!",
"visit_calendar": "Visiter le calendrier",
"wiki_location_desc": "Tire un extrait de l'article de Wikipedia correspondant au nom de l'emplacement.",
"will_be_marked_location": "sera marqué comme visité une fois l'emplacement enregistré.",
"no_locations_found": "Aucun emplacement trouvé"
}, },
"home": { "home": {
"desc_1": "Découvrez, planifiez et explorez en toute simplicité", "desc_1": "Découvrez, planifiez et explorez en toute simplicité",
@ -317,10 +321,10 @@
"public_tooltip": "Avec un profil public, les utilisateurs peuvent partager des collections avec vous et afficher votre profil sur la page des utilisateurs.", "public_tooltip": "Avec un profil public, les utilisateurs peuvent partager des collections avec vous et afficher votre profil sur la page des utilisateurs.",
"new_password": "Nouveau mot de passe", "new_password": "Nouveau mot de passe",
"or_3rd_party": "Ou connectez-vous avec un service tiers", "or_3rd_party": "Ou connectez-vous avec un service tiers",
"no_public_adventures": "Aucune aventure publique trouvée",
"no_public_collections": "Aucune collection publique trouvée", "no_public_collections": "Aucune collection publique trouvée",
"user_adventures": "Aventures de l'utilisateur", "user_collections": "Collections de l'utilisateur",
"user_collections": "Collections de l'utilisateur" "no_public_locations": "Aucun emplacement public trouvé",
"user_locations": "Emplacements des utilisateurs"
}, },
"users": { "users": {
"no_users_found": "Aucun utilisateur trouvé avec un profil public." "no_users_found": "Aucun utilisateur trouvé avec un profil public."
@ -568,7 +572,13 @@
"search": { "search": {
"adventurelog_results": "Résultats dans AdventureLog", "adventurelog_results": "Résultats dans AdventureLog",
"online_results": "Résultats en ligne", "online_results": "Résultats en ligne",
"public_adventures": "Aventures publiques" "public_adventures": "Aventures publiques",
"cities": "Villes",
"countries": "Pays",
"found": "trouvé",
"result": "Résultat",
"results": "Résultats",
"try_searching_desc": "Essayez de rechercher des aventures, des collections, des pays, des régions, des villes ou des utilisateurs."
}, },
"map": { "map": {
"add_adventure": "Ajouter une nouvelle aventure", "add_adventure": "Ajouter une nouvelle aventure",
@ -579,13 +589,16 @@
"show_visited_regions": "Afficher les régions visitées", "show_visited_regions": "Afficher les régions visitées",
"view_details": "Afficher les détails", "view_details": "Afficher les détails",
"adventure_stats": "Statistiques d'aventure", "adventure_stats": "Statistiques d'aventure",
"adventures_shown": "aventures montrées",
"completion": "Achèvement", "completion": "Achèvement",
"display_options": "Options d'affichage", "display_options": "Options d'affichage",
"map_controls": "Contrôles de cartes", "map_controls": "Contrôles de cartes",
"marker_placed_on_map": "Marqueur placé sur la carte", "marker_placed_on_map": "Marqueur placé sur la carte",
"place_marker_desc": "Cliquez sur la carte pour placer un marqueur ou ajouter une aventure sans emplacement.", "regions": "Régions",
"regions": "Régions" "add_location": "Ajouter un nouvel emplacement",
"add_location_at_marker": "Ajouter un nouvel emplacement chez Marker",
"location_map": "Carte de localisation",
"locations_shown": "Emplacements montrés",
"place_marker_desc_location": "Cliquez sur la carte pour placer un marqueur."
}, },
"languages": {}, "languages": {},
"share": { "share": {
@ -611,9 +624,9 @@
"no_shared_adventures": "Cet utilisateur n'a encore partagé aucune aventure publique.", "no_shared_adventures": "Cet utilisateur n'a encore partagé aucune aventure publique.",
"no_shared_collections": "Cet utilisateur n'a pas encore partagé de collections publiques.", "no_shared_collections": "Cet utilisateur n'a pas encore partagé de collections publiques.",
"planned_trips": "Voyages prévus", "planned_trips": "Voyages prévus",
"public_adventure_experiences": "Expériences d'aventure publique",
"travel_statistics": "Statistiques de voyage", "travel_statistics": "Statistiques de voyage",
"your_journey_at_a_glance": "Votre voyage d'aventure en un coup d'œil" "your_journey_at_a_glance": "Votre voyage d'aventure en un coup d'œil",
"public_location_experiences": "Expériences de localisation publique"
}, },
"categories": { "categories": {
"category_name": "Nom de la catégorie", "category_name": "Nom de la catégorie",
@ -622,9 +635,9 @@
"manage_categories": "Gérer les catégories", "manage_categories": "Gérer les catégories",
"no_categories_found": "Aucune catégorie trouvée.", "no_categories_found": "Aucune catégorie trouvée.",
"select_category": "Sélectionnez une catégorie", "select_category": "Sélectionnez une catégorie",
"update_after_refresh": "Les cartes d'aventure seront mises à jour une fois que vous aurez actualisé la page.",
"add_new_category": "Ajouter une nouvelle catégorie", "add_new_category": "Ajouter une nouvelle catégorie",
"name_required": "Le nom de catégorie est requis" "name_required": "Le nom de catégorie est requis",
"location_update_after_refresh": "Les cartes de localisation seront mises à jour une fois que vous avez actualisé la page."
}, },
"dashboard": { "dashboard": {
"add_some": "Pourquoi ne pas commencer à planifier votre prochaine aventure ? \nVous pouvez ajouter une nouvelle aventure en cliquant sur le bouton ci-dessous.", "add_some": "Pourquoi ne pas commencer à planifier votre prochaine aventure ? \nVous pouvez ajouter une nouvelle aventure en cliquant sur le bouton ci-dessous.",
@ -670,9 +683,9 @@
"recomendations": { "recomendations": {
"recommendation": "Recommandation", "recommendation": "Recommandation",
"recommendations": "Recommandations", "recommendations": "Recommandations",
"adventure_recommendations": "Recommandations d'aventure",
"food": "Nourriture", "food": "Nourriture",
"tourism": "Tourisme" "tourism": "Tourisme",
"location_recommendations": "Recommandations de localisation"
}, },
"lodging": { "lodging": {
"apartment": "Appartement", "apartment": "Appartement",
@ -695,17 +708,19 @@
"google_maps_integration_desc": "Connectez votre compte Google Maps pour obtenir des résultats de recherche et recommandations de recherche de haute qualité." "google_maps_integration_desc": "Connectez votre compte Google Maps pour obtenir des résultats de recherche et recommandations de recherche de haute qualité."
}, },
"calendar": { "calendar": {
"all_categories": "Toutes les catégories",
"all_day_event": "Événement toute la journée", "all_day_event": "Événement toute la journée",
"calendar_overview": "Aperçu du calendrier", "calendar_overview": "Aperçu du calendrier",
"categories": "Catégories",
"day": "Jour", "day": "Jour",
"events_scheduled": "événements prévus", "events_scheduled": "événements prévus",
"filter_by_category": "Filtre par catégorie",
"filtered_results": "Résultats filtrés", "filtered_results": "Résultats filtrés",
"month": "Mois", "month": "Mois",
"today": "Aujourd'hui", "today": "Aujourd'hui",
"total_events": "Événements totaux", "total_events": "Événements totaux",
"week": "Semaine" "week": "Semaine"
},
"locations": {
"location": "Emplacement",
"locations": "Lieux",
"my_locations": "Mes emplacements"
} }
} }

View file

@ -15,7 +15,6 @@
"activities": {}, "activities": {},
"add_to_collection": "Aggiungi alla collezione", "add_to_collection": "Aggiungi alla collezione",
"adventure": "Avventura", "adventure": "Avventura",
"adventure_delete_confirm": "Sei sicuro di voler eliminare questa avventura? \nQuesta azione non può essere annullata.",
"archive": "Archivio", "archive": "Archivio",
"archived": "Archiviato", "archived": "Archiviato",
"archived_collection_message": "Collezione archiviata con successo!", "archived_collection_message": "Collezione archiviata con successo!",
@ -25,9 +24,6 @@
"category_filter": "Filtro categoria", "category_filter": "Filtro categoria",
"clear": "Rimuovere", "clear": "Rimuovere",
"collection": "Collezione", "collection": "Collezione",
"collection_link_error": "Errore nel collegamento dell'avventura alla collezione",
"collection_remove_error": "Errore durante la rimozione dell'avventura dalla collezione",
"collection_remove_success": "Avventura rimossa con successo dalla collezione!",
"count_txt": "risultati corrispondenti alla tua ricerca", "count_txt": "risultati corrispondenti alla tua ricerca",
"create_new": "Crea nuovo...", "create_new": "Crea nuovo...",
"date": "Data", "date": "Data",
@ -44,8 +40,6 @@
"my_collections": "Le mie collezioni", "my_collections": "Le mie collezioni",
"name": "Nome", "name": "Nome",
"no_image_found": "Nessuna immagine trovata", "no_image_found": "Nessuna immagine trovata",
"not_found": "Avventura non trovata",
"not_found_desc": "L'avventura che stavi cercando non è stata trovata. \nProva un'avventura diversa o riprova più tardi.",
"open_details": "Apri Dettagli", "open_details": "Apri Dettagli",
"open_filters": "Apri filtri", "open_filters": "Apri filtri",
"order_by": "Ordina per", "order_by": "Ordina per",
@ -62,11 +56,8 @@
"updated": "Aggiornato", "updated": "Aggiornato",
"visit": "Visita", "visit": "Visita",
"visits": "Visite", "visits": "Visite",
"adventure_delete_success": "Avventura eliminata con successo!",
"collection_adventures": "Includi avventure dalle raccolte", "collection_adventures": "Includi avventure dalle raccolte",
"collection_link_success": "Avventura collegata alla collezione con successo!",
"dates": "Date", "dates": "Date",
"delete_adventure": "Elimina avventura",
"duration": "Durata", "duration": "Durata",
"image_removed_error": "Errore durante la rimozione dell'immagine", "image_removed_error": "Errore durante la rimozione dell'immagine",
"image_removed_success": "Immagine rimossa con successo!", "image_removed_success": "Immagine rimossa con successo!",
@ -80,10 +71,6 @@
"activity_types": "Tipi di attività", "activity_types": "Tipi di attività",
"add": "Aggiungere", "add": "Aggiungere",
"add_notes": "Aggiungi note", "add_notes": "Aggiungi note",
"adventure_create_error": "Impossibile creare l'avventura",
"adventure_created": "Avventura creata",
"adventure_update_error": "Impossibile aggiornare l'avventura",
"adventure_updated": "Avventura aggiornata",
"basic_information": "Informazioni di base", "basic_information": "Informazioni di base",
"category": "Categoria", "category": "Categoria",
"clear_map": "Libera mappa", "clear_map": "Libera mappa",
@ -99,23 +86,19 @@
"location": "Posizione", "location": "Posizione",
"location_information": "Informazioni sulla posizione", "location_information": "Informazioni sulla posizione",
"my_images": "Le mie immagini", "my_images": "Le mie immagini",
"new_adventure": "Nuova avventura",
"no_description_found": "Nessuna descrizione trovata", "no_description_found": "Nessuna descrizione trovata",
"no_images": "Nessuna immagine", "no_images": "Nessuna immagine",
"no_location": "Inserisci una località", "no_location": "Inserisci una località",
"no_results": "Nessun risultato trovato", "no_results": "Nessun risultato trovato",
"public_adventure": "Avventura pubblica",
"remove": "Rimuovere", "remove": "Rimuovere",
"save_next": "Salva", "save_next": "Salva",
"search_for_location": "Cerca una posizione", "search_for_location": "Cerca una posizione",
"search_results": "Risultati della ricerca", "search_results": "Risultati della ricerca",
"see_adventures": "Vedi Avventure", "see_adventures": "Vedi Avventure",
"share_adventure": "Condividi questa avventura!",
"start_date": "Data di inizio", "start_date": "Data di inizio",
"upload_image": "Carica immagine", "upload_image": "Carica immagine",
"url": "URL", "url": "URL",
"warning": "Avvertimento", "warning": "Avvertimento",
"wiki_desc": "Estrae un estratto dall'articolo di Wikipedia corrispondente al nome dell'avventura.",
"wiki_image_error": "Errore durante il recupero dell'immagine da Wikipedia", "wiki_image_error": "Errore durante il recupero dell'immagine da Wikipedia",
"wikipedia": "Wikipedia", "wikipedia": "Wikipedia",
"adventure_not_found": "Non ci sono avventure da visualizzare. \nAggiungine alcuni utilizzando il pulsante più in basso a destra o prova a cambiare i filtri!", "adventure_not_found": "Non ci sono avventure da visualizzare. \nAggiungine alcuni utilizzando il pulsante più in basso a destra o prova a cambiare i filtri!",
@ -124,7 +107,6 @@
"mark_visited": "Segna come visitato", "mark_visited": "Segna come visitato",
"my_adventures": "Le mie avventure", "my_adventures": "Le mie avventure",
"no_adventures_found": "Nessuna avventura trovata", "no_adventures_found": "Nessuna avventura trovata",
"no_collections_found": "Nessuna collezione trovata a cui aggiungere questa avventura.",
"no_linkable_adventures": "Non è stata trovata alcuna avventura che possa essere collegata a questa collezione.", "no_linkable_adventures": "Non è stata trovata alcuna avventura che possa essere collegata a questa collezione.",
"not_visited": "Non visitato", "not_visited": "Non visitato",
"regions_updated": "regioni aggiornate", "regions_updated": "regioni aggiornate",
@ -181,10 +163,7 @@
"starting_airport": "Aeroporto di partenza", "starting_airport": "Aeroporto di partenza",
"to": "A", "to": "A",
"transportation_delete_confirm": "Sei sicuro di voler eliminare questo trasporto? \nQuesta azione non può essere annullata.", "transportation_delete_confirm": "Sei sicuro di voler eliminare questo trasporto? \nQuesta azione non può essere annullata.",
"will_be_marked": "verrà contrassegnato come visitato una volta salvata l'avventura.",
"cities_updated": "città aggiornate", "cities_updated": "città aggiornate",
"create_adventure": "Crea Avventura",
"no_adventures_to_recommendations": "Nessuna avventura trovata. \nAggiungi almeno un'avventura per ricevere consigli.",
"finding_recommendations": "Alla scoperta di tesori nascosti per la tua prossima avventura", "finding_recommendations": "Alla scoperta di tesori nascosti per la tua prossima avventura",
"attachment": "Allegato", "attachment": "Allegato",
"attachment_delete_success": "Allegato eliminato con successo!", "attachment_delete_success": "Allegato eliminato con successo!",
@ -246,7 +225,32 @@
"name_location": "Nome, posizione", "name_location": "Nome, posizione",
"collection_contents": "Contenuto di raccolta", "collection_contents": "Contenuto di raccolta",
"check_in": "Check -in", "check_in": "Check -in",
"check_out": "Guardare" "check_out": "Guardare",
"collection_link_location_error": "Errore che collega la posizione alla raccolta",
"collection_link_location_success": "Posizione collegata alla raccolta con successo!",
"collection_locations": "Includi luoghi di raccolta",
"collection_remove_location_error": "Errore di rimozione della posizione dalla raccolta",
"collection_remove_location_success": "Posizione rimossa dalla raccolta con successo!",
"create_location": "Crea posizione",
"delete_location": "Elimina posizione",
"edit_location": "Modifica posizione",
"location_create_error": "Impossibile creare posizione",
"location_created": "Posizione creata",
"location_delete_confirm": "Sei sicuro di voler eliminare questa posizione? \nQuesta azione non può essere annullata.",
"location_delete_success": "Posizione eliminata con successo!",
"location_not_found": "Posizione non trovata",
"location_not_found_desc": "Non è stato possibile trovare la posizione che stavi cercando. \nProva una posizione diversa o ricontrolla più tardi.",
"location_update_error": "Impossibile aggiornare la posizione",
"location_updated": "Posizione aggiornata",
"new_location": "Nuova posizione",
"no_collections_to_add_location": "Nessuna collezione trovata per aggiungere questa posizione a.",
"no_locations_to_recommendations": "Nessuna posizione trovata. \nAggiungi almeno una posizione per ottenere consigli.",
"public_location": "Posizione pubblica",
"share_location": "Condividi questa posizione!",
"visit_calendar": "Visita il calendario",
"wiki_location_desc": "Estratto dall'articolo di Wikipedia che corrisponde al nome della posizione.",
"will_be_marked_location": "sarà contrassegnato come visitato una volta salvata la posizione.",
"no_locations_found": "Nessuna posizione trovata"
}, },
"home": { "home": {
"desc_1": "Scopri, pianifica ed esplora con facilità", "desc_1": "Scopri, pianifica ed esplora con facilità",
@ -317,10 +321,10 @@
"public_tooltip": "Con un profilo pubblico, gli utenti possono condividere raccolte con te e visualizzare il tuo profilo nella pagina degli utenti.", "public_tooltip": "Con un profilo pubblico, gli utenti possono condividere raccolte con te e visualizzare il tuo profilo nella pagina degli utenti.",
"new_password": "Nuova password", "new_password": "Nuova password",
"or_3rd_party": "Oppure accedi con un servizio di terze parti", "or_3rd_party": "Oppure accedi con un servizio di terze parti",
"no_public_adventures": "Nessuna avventura pubblica trovata",
"no_public_collections": "Nessuna collezione pubblica trovata", "no_public_collections": "Nessuna collezione pubblica trovata",
"user_adventures": "Avventure utente", "user_collections": "Collezioni utente",
"user_collections": "Collezioni utente" "no_public_locations": "Nessuna posizione pubblica trovata",
"user_locations": "Posizioni degli utenti"
}, },
"users": { "users": {
"no_users_found": "Nessun utente trovato con profili pubblici." "no_users_found": "Nessun utente trovato con profili pubblici."
@ -568,7 +572,13 @@
"search": { "search": {
"adventurelog_results": "Risultati di AdventureLog", "adventurelog_results": "Risultati di AdventureLog",
"online_results": "Risultati in linea", "online_results": "Risultati in linea",
"public_adventures": "Avventure pubbliche" "public_adventures": "Avventure pubbliche",
"cities": "Città",
"countries": "Paesi",
"found": "trovato",
"result": "Risultato",
"results": "Risultati",
"try_searching_desc": "Prova a cercare avventure, collezioni, paesi, regioni, città o utenti."
}, },
"map": { "map": {
"add_adventure": "Aggiungi nuova avventura", "add_adventure": "Aggiungi nuova avventura",
@ -579,13 +589,16 @@
"show_visited_regions": "Mostra regioni visitate", "show_visited_regions": "Mostra regioni visitate",
"view_details": "Visualizza dettagli", "view_details": "Visualizza dettagli",
"adventure_stats": "Statistiche di avventura", "adventure_stats": "Statistiche di avventura",
"adventures_shown": "Avventure mostrate",
"completion": "Completamento", "completion": "Completamento",
"display_options": "Opzioni di visualizzazione", "display_options": "Opzioni di visualizzazione",
"map_controls": "Controlli della mappa", "map_controls": "Controlli della mappa",
"marker_placed_on_map": "Marcatore posizionato sulla mappa", "marker_placed_on_map": "Marcatore posizionato sulla mappa",
"place_marker_desc": "Fai clic sulla mappa per posizionare un indicatore o aggiungi un'avventura senza posizione.", "regions": "Regioni",
"regions": "Regioni" "add_location": "Aggiungi nuova posizione",
"add_location_at_marker": "Aggiungi nuova posizione al marcatore",
"location_map": "Mappa della posizione",
"locations_shown": "posizioni mostrate",
"place_marker_desc_location": "Fai clic sulla mappa per posizionare un indicatore."
}, },
"languages": {}, "languages": {},
"share": { "share": {
@ -611,9 +624,9 @@
"no_shared_adventures": "Questo utente non ha ancora condiviso avventure pubbliche.", "no_shared_adventures": "Questo utente non ha ancora condiviso avventure pubbliche.",
"no_shared_collections": "Questo utente non ha ancora condiviso alcuna collezione pubblica.", "no_shared_collections": "Questo utente non ha ancora condiviso alcuna collezione pubblica.",
"planned_trips": "Viaggi pianificati", "planned_trips": "Viaggi pianificati",
"public_adventure_experiences": "Esperienze di avventura pubblica",
"travel_statistics": "Statistiche di viaggio", "travel_statistics": "Statistiche di viaggio",
"your_journey_at_a_glance": "Il tuo viaggio d'avventura a colpo d'occhio" "your_journey_at_a_glance": "Il tuo viaggio d'avventura a colpo d'occhio",
"public_location_experiences": "Esperienze di posizione pubblica"
}, },
"categories": { "categories": {
"category_name": "Nome della categoria", "category_name": "Nome della categoria",
@ -622,9 +635,9 @@
"manage_categories": "Gestisci categorie", "manage_categories": "Gestisci categorie",
"no_categories_found": "Nessuna categoria trovata.", "no_categories_found": "Nessuna categoria trovata.",
"select_category": "Seleziona Categoria", "select_category": "Seleziona Categoria",
"update_after_refresh": "Le carte avventura verranno aggiornate una volta aggiornata la pagina.",
"add_new_category": "Aggiungi nuova categoria", "add_new_category": "Aggiungi nuova categoria",
"name_required": "È richiesto il nome della categoria" "name_required": "È richiesto il nome della categoria",
"location_update_after_refresh": "Le schede di posizione verranno aggiornate dopo aver aggiornato la pagina."
}, },
"dashboard": { "dashboard": {
"add_some": "Perché non iniziare a pianificare la tua prossima avventura? \nPuoi aggiungere una nuova avventura facendo clic sul pulsante in basso.", "add_some": "Perché non iniziare a pianificare la tua prossima avventura? \nPuoi aggiungere una nuova avventura facendo clic sul pulsante in basso.",
@ -670,9 +683,9 @@
"recomendations": { "recomendations": {
"recommendation": "Raccomandazione", "recommendation": "Raccomandazione",
"recommendations": "Raccomandazioni", "recommendations": "Raccomandazioni",
"adventure_recommendations": "Consigli di avventura",
"food": "Cibo", "food": "Cibo",
"tourism": "Turismo" "tourism": "Turismo",
"location_recommendations": "Raccomandazioni sulla posizione"
}, },
"lodging": { "lodging": {
"apartment": "Appartamento", "apartment": "Appartamento",
@ -695,17 +708,19 @@
"google_maps_integration_desc": "Collega il tuo account Google Maps per ottenere risultati e consigli di ricerca sulla posizione di alta qualità." "google_maps_integration_desc": "Collega il tuo account Google Maps per ottenere risultati e consigli di ricerca sulla posizione di alta qualità."
}, },
"calendar": { "calendar": {
"all_categories": "Tutte le categorie",
"all_day_event": "Evento per tutto il giorno", "all_day_event": "Evento per tutto il giorno",
"calendar_overview": "Panoramica del calendario", "calendar_overview": "Panoramica del calendario",
"categories": "Categorie",
"day": "Giorno", "day": "Giorno",
"events_scheduled": "eventi programmati", "events_scheduled": "eventi programmati",
"filter_by_category": "Filtro per categoria",
"filtered_results": "Risultati filtrati", "filtered_results": "Risultati filtrati",
"month": "Mese", "month": "Mese",
"today": "Oggi", "today": "Oggi",
"total_events": "Eventi totali", "total_events": "Eventi totali",
"week": "Settimana" "week": "Settimana"
},
"locations": {
"location": "Posizione",
"locations": "Luoghi",
"my_locations": "Le mie posizioni"
} }
} }

View file

@ -22,13 +22,7 @@
"add_to_collection": "컬렉션에 추가하세요", "add_to_collection": "컬렉션에 추가하세요",
"adventure": "모험", "adventure": "모험",
"adventure_calendar": "모험 달력", "adventure_calendar": "모험 달력",
"adventure_create_error": "모험을 만들지 못했습니다",
"adventure_created": "모험 생성됨",
"adventure_delete_confirm": "이 모험을 삭제 하시겠습니까? 이 행동은 취소 할 수 없습니다.",
"adventure_delete_success": "모험이 성공적으로 삭제되었습니다!",
"adventure_not_found": "표시할 모험이 없습니다. 오른쪽 하단의 플러스 버튼을 사용하여 추가하거나 필터를 변경하세요!", "adventure_not_found": "표시할 모험이 없습니다. 오른쪽 하단의 플러스 버튼을 사용하여 추가하거나 필터를 변경하세요!",
"adventure_update_error": "모험을 업데이트하지 못했습니다",
"adventure_updated": "모험 업데이트됨",
"all": "모두", "all": "모두",
"archive": "보관", "archive": "보관",
"archived": "보관됨", "archived": "보관됨",
@ -56,16 +50,11 @@
"collection_adventures": "컬렉션 모험을 추가하세요", "collection_adventures": "컬렉션 모험을 추가하세요",
"collection_archived": "이 컬렉션은 보관되었습니다.", "collection_archived": "이 컬렉션은 보관되었습니다.",
"collection_completed": "이 컬렉션을 완성했습니다!", "collection_completed": "이 컬렉션을 완성했습니다!",
"collection_link_error": "컬렉션에 모험 연결 중 오류",
"collection_link_success": "컬렉션에 모험이 성공적으로 연결되었습니다!",
"collection_remove_error": "컬렉션에서 모험을 제거 중 오류",
"collection_remove_success": "컬렉션에서 모험이 제거되었습니다!",
"collection_stats": "컬렉션 통계", "collection_stats": "컬렉션 통계",
"copied_to_clipboard": "클립 보드에 복사됨!", "copied_to_clipboard": "클립 보드에 복사됨!",
"copy_failed": "복사 실패", "copy_failed": "복사 실패",
"copy_link": "링크 복사", "copy_link": "링크 복사",
"count_txt": "검색과 일치하는 결과", "count_txt": "검색과 일치하는 결과",
"create_adventure": "모험 생성",
"create_new": "새로 만들기...", "create_new": "새로 만들기...",
"date": "일자", "date": "일자",
"date_constrain": "컬렉션 일자로 제한", "date_constrain": "컬렉션 일자로 제한",
@ -74,7 +63,6 @@
"day": "일", "day": "일",
"days": "일", "days": "일",
"delete": "삭제", "delete": "삭제",
"delete_adventure": "모험 삭제",
"delete_checklist": "체크리스트 삭제", "delete_checklist": "체크리스트 삭제",
"delete_collection": "컬렉션 삭제", "delete_collection": "컬렉션 삭제",
"delete_collection_success": "컬렉션이 성공적으로 삭제되었습니다!", "delete_collection_success": "컬렉션이 성공적으로 삭제되었습니다!",
@ -123,10 +111,7 @@
"my_collections": "내 컬렉션", "my_collections": "내 컬렉션",
"my_images": "내 이미지", "my_images": "내 이미지",
"name": "이름", "name": "이름",
"new_adventure": "새로운 모험",
"no_adventures_found": "모험이 없습니다", "no_adventures_found": "모험이 없습니다",
"no_adventures_to_recommendations": "모험이 없습니다. 장소를 추천받으려면 최소 하나 이상의 모험을 등록해야 합니다.",
"no_collections_found": "이 모험을 추가할 수 있는 컬렉션이 없습니다.",
"no_description_found": "설명이 없습니다", "no_description_found": "설명이 없습니다",
"no_image_found": "이미지가 없습니다", "no_image_found": "이미지가 없습니다",
"no_image_url": "해당 URL에 이미지가 없습니다.", "no_image_url": "해당 URL에 이미지가 없습니다.",
@ -135,8 +120,6 @@
"no_location": "위치를 입력하세요", "no_location": "위치를 입력하세요",
"no_location_found": "위치가 없습니다", "no_location_found": "위치가 없습니다",
"no_results": "결과가 없습니다", "no_results": "결과가 없습니다",
"not_found": "모험이 없습니다",
"not_found_desc": "당신이 찾고 있던 모험을 찾을 수 없었습니다. 다른 모험을 찾아보거나 나중에 다시 해 보세요.",
"not_visited": "방문하지 않음", "not_visited": "방문하지 않음",
"note": "노트", "note": "노트",
"note_delete_confirm": "이 노트를 삭제 하시겠습니까? 이 행동은 취소 할 수 없습니다.", "note_delete_confirm": "이 노트를 삭제 하시겠습니까? 이 행동은 취소 할 수 없습니다.",
@ -151,7 +134,6 @@
"preview": "미리보기", "preview": "미리보기",
"private": "비공개", "private": "비공개",
"public": "공개", "public": "공개",
"public_adventure": "공개 모험",
"rating": "평가", "rating": "평가",
"regions_updated": "지역이 업데이트되었습니다", "regions_updated": "지역이 업데이트되었습니다",
"remove": "제거", "remove": "제거",
@ -162,7 +144,6 @@
"see_adventures": "모험 보기", "see_adventures": "모험 보기",
"set_to_pin": "고정하기", "set_to_pin": "고정하기",
"share": "공유", "share": "공유",
"share_adventure": "이 모험을 공유하세요!",
"show": "보기", "show": "보기",
"sort": "정렬", "sort": "정렬",
"sources": "출처", "sources": "출처",
@ -190,10 +171,8 @@
"visited_region_check_desc": "이것을 선택하면 서버는 방문한 모든 모험을 확인하고 그 모험의 지역을 표시하여 세계 여행에서 방문 여부를 표시합니다.", "visited_region_check_desc": "이것을 선택하면 서버는 방문한 모든 모험을 확인하고 그 모험의 지역을 표시하여 세계 여행에서 방문 여부를 표시합니다.",
"visits": "방문", "visits": "방문",
"warning": "경고", "warning": "경고",
"wiki_desc": "모험의 이름과 일치하는 글을 위키백과에서 가져옵니다.",
"wiki_image_error": "위키백과 이미지 가져오기 오류", "wiki_image_error": "위키백과 이미지 가져오기 오류",
"wikipedia": "위키백과", "wikipedia": "위키백과",
"will_be_marked": "모험이 저장되면 방문했다고 표시합니다.",
"checklists": "체크리스트", "checklists": "체크리스트",
"cities_updated": "도시 업데이트됨", "cities_updated": "도시 업데이트됨",
"clear": "초기화", "clear": "초기화",
@ -246,7 +225,32 @@
"name_location": "이름, 위치", "name_location": "이름, 위치",
"collection_contents": "수집 내용", "collection_contents": "수집 내용",
"check_in": "체크인", "check_in": "체크인",
"check_out": "체크 아웃" "check_out": "체크 아웃",
"collection_link_location_error": "오류 연결 위치 컬렉션",
"collection_link_location_success": "컬렉션에 링크 된 위치!",
"collection_locations": "수집 위치를 포함합니다",
"collection_remove_location_error": "수집에서 위치를 제거하는 오류",
"collection_remove_location_success": "컬렉션에서 제거 된 위치는 성공적으로!",
"create_location": "위치를 만듭니다",
"delete_location": "위치 삭제",
"edit_location": "위치 편집",
"location_create_error": "위치를 만들지 못했습니다",
"location_created": "생성 된 위치",
"location_delete_confirm": "이 위치를 삭제 하시겠습니까? \n이 조치는 취소 할 수 없습니다.",
"location_delete_success": "위치가 성공적으로 삭제되었습니다!",
"location_not_found": "위치를 찾을 수 없습니다",
"location_not_found_desc": "당신이 찾고 있던 위치는 찾을 수 없습니다. \n다른 위치를 시도하거나 나중에 다시 확인하십시오.",
"location_update_error": "위치를 업데이트하지 못했습니다",
"location_updated": "위치 업데이트",
"new_location": "새로운 위치",
"no_collections_to_add_location": "이 위치를 추가하는 컬렉션은 없습니다.",
"no_locations_to_recommendations": "발견 된 위치는 없습니다. \n권장 사항을 얻으려면 하나 이상의 위치를 ​​추가하십시오.",
"public_location": "공개 위치",
"share_location": "이 위치를 공유하십시오!",
"visit_calendar": "캘린더를 방문하십시오",
"wiki_location_desc": "위치 이름과 일치하는 Wikipedia 기사에서 발췌 한 내용을 가져옵니다.",
"will_be_marked_location": "위치가 저장되면 방문한대로 표시됩니다.",
"no_locations_found": "발견 된 위치는 없습니다"
}, },
"auth": { "auth": {
"confirm_password": "비밀번호 확인", "confirm_password": "비밀번호 확인",
@ -265,10 +269,10 @@
"registration_disabled": "현재 등록할 수 없습니다.", "registration_disabled": "현재 등록할 수 없습니다.",
"signup": "가입", "signup": "가입",
"username": "사용자 이름", "username": "사용자 이름",
"no_public_adventures": "공개 모험이 발견되지 않았습니다",
"no_public_collections": "공개 컬렉션이 발견되지 않았습니다", "no_public_collections": "공개 컬렉션이 발견되지 않았습니다",
"user_adventures": "사용자 모험", "user_collections": "사용자 수집",
"user_collections": "사용자 수집" "no_public_locations": "공공 장소가 발견되지 않았습니다",
"user_locations": "사용자 위치"
}, },
"categories": { "categories": {
"category_name": "카테고리 이름", "category_name": "카테고리 이름",
@ -277,9 +281,9 @@
"manage_categories": "카테고리 관리", "manage_categories": "카테고리 관리",
"no_categories_found": "카테고리가 없습니다.", "no_categories_found": "카테고리가 없습니다.",
"select_category": "카테고리 선택", "select_category": "카테고리 선택",
"update_after_refresh": "페이지를 새로고침해야 모험 카드가 업데이트됩니다.",
"add_new_category": "새 카테고리를 추가하십시오", "add_new_category": "새 카테고리를 추가하십시오",
"name_required": "카테고리 이름이 필요합니다" "name_required": "카테고리 이름이 필요합니다",
"location_update_after_refresh": "페이지를 새로 고치면 위치 카드가 업데이트됩니다."
}, },
"checklist": { "checklist": {
"checklist_delete_error": "체크리스트 삭제 오류", "checklist_delete_error": "체크리스트 삭제 오류",
@ -381,13 +385,16 @@
"show_visited_regions": "방문한 지역 보기", "show_visited_regions": "방문한 지역 보기",
"view_details": "상세 보기", "view_details": "상세 보기",
"adventure_stats": "모험 통계", "adventure_stats": "모험 통계",
"adventures_shown": "모험 쇼",
"completion": "완성", "completion": "완성",
"display_options": "디스플레이 옵션", "display_options": "디스플레이 옵션",
"map_controls": "맵 컨트롤", "map_controls": "맵 컨트롤",
"marker_placed_on_map": "마커가지도에 배치되었습니다", "marker_placed_on_map": "마커가지도에 배치되었습니다",
"place_marker_desc": "지도를 클릭하여 마커를 배치하거나 위치없이 모험을 추가하십시오.", "regions": "지역",
"regions": "지역" "add_location": "새로운 위치를 추가하십시오",
"add_location_at_marker": "마커에 새 위치를 추가하십시오",
"location_map": "위치지도",
"locations_shown": "표시된 위치",
"place_marker_desc_location": "지도를 클릭하여 마커를 배치하십시오."
}, },
"navbar": { "navbar": {
"about": "Adventurelog 소개", "about": "Adventurelog 소개",
@ -448,21 +455,27 @@
"no_shared_adventures": "이 사용자는 아직 공개 모험을 공유하지 않았습니다.", "no_shared_adventures": "이 사용자는 아직 공개 모험을 공유하지 않았습니다.",
"no_shared_collections": "이 사용자는 아직 공개 컬렉션을 공유하지 않았습니다.", "no_shared_collections": "이 사용자는 아직 공개 컬렉션을 공유하지 않았습니다.",
"planned_trips": "계획된 여행", "planned_trips": "계획된 여행",
"public_adventure_experiences": "공개 모험 경험",
"your_journey_at_a_glance": "당신의 모험 여행", "your_journey_at_a_glance": "당신의 모험 여행",
"travel_statistics": "여행 통계" "travel_statistics": "여행 통계",
"public_location_experiences": "공개 위치 경험"
}, },
"recomendations": { "recomendations": {
"recommendation": "추천", "recommendation": "추천",
"recommendations": "권장 사항", "recommendations": "권장 사항",
"adventure_recommendations": "모험 추천",
"food": "음식", "food": "음식",
"tourism": "관광 여행" "tourism": "관광 여행",
"location_recommendations": "위치 권장 사항"
}, },
"search": { "search": {
"adventurelog_results": "Adventurelog 결과", "adventurelog_results": "Adventurelog 결과",
"online_results": "온라인 결과", "online_results": "온라인 결과",
"public_adventures": "공개 모험" "public_adventures": "공개 모험",
"cities": "도시",
"countries": "국가",
"found": "설립하다",
"result": "결과",
"results": "결과",
"try_searching_desc": "모험, 컬렉션, 국가, 지역, 도시 또는 사용자를 검색하십시오."
}, },
"settings": { "settings": {
"about_this_background": "이 배경에 대해", "about_this_background": "이 배경에 대해",
@ -694,17 +707,19 @@
"google_maps_integration_desc": "Google지도 계정을 연결하여 고품질 위치 검색 결과 및 권장 사항을 얻으십시오." "google_maps_integration_desc": "Google지도 계정을 연결하여 고품질 위치 검색 결과 및 권장 사항을 얻으십시오."
}, },
"calendar": { "calendar": {
"all_categories": "모든 카테고리",
"all_day_event": "하루 종일 이벤트", "all_day_event": "하루 종일 이벤트",
"calendar_overview": "캘린더 개요", "calendar_overview": "캘린더 개요",
"categories": "카테고리",
"day": "낮", "day": "낮",
"events_scheduled": "예약 된 이벤트", "events_scheduled": "예약 된 이벤트",
"filter_by_category": "카테고리 별 필터",
"filtered_results": "필터링 된 결과", "filtered_results": "필터링 된 결과",
"month": "월", "month": "월",
"today": "오늘", "today": "오늘",
"total_events": "총 이벤트", "total_events": "총 이벤트",
"week": "주" "week": "주"
},
"locations": {
"location": "위치",
"locations": "위치",
"my_locations": "내 위치"
} }
} }

View file

@ -15,7 +15,6 @@
"activities": {}, "activities": {},
"add_to_collection": "Toevoegen aan collectie", "add_to_collection": "Toevoegen aan collectie",
"adventure": "Avontuur", "adventure": "Avontuur",
"adventure_delete_confirm": "Weet je zeker dat je dit avontuur wilt verwijderen? \nDeze actie kan niet ongedaan worden gemaakt.",
"archive": "Archiveer", "archive": "Archiveer",
"archived": "Gearchiveerd", "archived": "Gearchiveerd",
"archived_collection_message": "Collectie succesvol gearchiveerd!", "archived_collection_message": "Collectie succesvol gearchiveerd!",
@ -26,10 +25,6 @@
"clear": "Leegmaken", "clear": "Leegmaken",
"collection": "Collectie", "collection": "Collectie",
"collection_adventures": "Inclusief collectie-avonturen", "collection_adventures": "Inclusief collectie-avonturen",
"collection_link_error": "Fout bij het koppelen van dit avontuur aan de collectie",
"collection_link_success": "Avontuur succesvol gekoppeld aan collectie!",
"collection_remove_error": "Fout bij verwijderen van dit avontuur uit de collectie",
"collection_remove_success": "Avontuur is succesvol uit de collectie verwijderd!",
"count_txt": "resultaten die overeenkomen met uw zoekopdracht", "count_txt": "resultaten die overeenkomen met uw zoekopdracht",
"create_new": "Maak nieuw...", "create_new": "Maak nieuw...",
"date": "Datum", "date": "Datum",
@ -46,8 +41,6 @@
"my_collections": "Mijn collecties", "my_collections": "Mijn collecties",
"name": "Naam", "name": "Naam",
"no_image_found": "Geen afbeelding gevonden", "no_image_found": "Geen afbeelding gevonden",
"not_found": "Avontuur niet gevonden",
"not_found_desc": "Het avontuur waar je naar op zoek was, kon niet worden gevonden. \nProbeer een ander avontuur of kom later nog eens terug.",
"open_details": "Details openen", "open_details": "Details openen",
"open_filters": "Filters openen", "open_filters": "Filters openen",
"order_by": "Sorteer op", "order_by": "Sorteer op",
@ -64,9 +57,7 @@
"updated": "Gewijzigd", "updated": "Gewijzigd",
"visit": "Bezoek", "visit": "Bezoek",
"visits": "Bezoeken", "visits": "Bezoeken",
"adventure_delete_success": "Avontuur succesvol verwijderd!",
"dates": "Datums", "dates": "Datums",
"delete_adventure": "Avontuur verwijderen",
"duration": "Duur", "duration": "Duur",
"image_removed_error": "Fout bij het verwijderen van de afbeelding", "image_removed_error": "Fout bij het verwijderen van de afbeelding",
"image_removed_success": "Afbeelding succesvol verwijderd!", "image_removed_success": "Afbeelding succesvol verwijderd!",
@ -81,10 +72,6 @@
"activity_types": "Activiteitstypen", "activity_types": "Activiteitstypen",
"add": "Toevoegen", "add": "Toevoegen",
"add_notes": "Voeg opmerkingen toe", "add_notes": "Voeg opmerkingen toe",
"adventure_create_error": "Kan geen avontuur aanmaken",
"adventure_created": "Avontuur aangemaakt",
"adventure_update_error": "Kan avontuur niet wijzigen",
"adventure_updated": "Avontuur gewijzigd",
"basic_information": "Basisinformatie", "basic_information": "Basisinformatie",
"category": "Categorie", "category": "Categorie",
"clear_map": "Kaart leegmaken", "clear_map": "Kaart leegmaken",
@ -100,23 +87,19 @@
"location": "Locatie", "location": "Locatie",
"location_information": "Informatie over de locatie", "location_information": "Informatie over de locatie",
"my_images": "Mijn afbeeldingen", "my_images": "Mijn afbeeldingen",
"new_adventure": "Nieuw avontuur",
"no_description_found": "Geen beschrijving gevonden", "no_description_found": "Geen beschrijving gevonden",
"no_images": "Geen afbeeldingen", "no_images": "Geen afbeeldingen",
"no_location": "Voer een locatie in", "no_location": "Voer een locatie in",
"no_results": "Geen resultaten gevonden", "no_results": "Geen resultaten gevonden",
"public_adventure": "Openbaar avontuur",
"remove": "Verwijderen", "remove": "Verwijderen",
"save_next": "Opslaan & Volgende", "save_next": "Opslaan & Volgende",
"search_for_location": "Zoek een locatie", "search_for_location": "Zoek een locatie",
"search_results": "Zoekresultaten", "search_results": "Zoekresultaten",
"see_adventures": "Zie Avonturen", "see_adventures": "Zie Avonturen",
"share_adventure": "Deel dit avontuur!",
"start_date": "Startdatum", "start_date": "Startdatum",
"upload_image": "Afbeelding uploaden", "upload_image": "Afbeelding uploaden",
"url": "URL", "url": "URL",
"warning": "Waarschuwing", "warning": "Waarschuwing",
"wiki_desc": "Haalt een fragment uit een Wikipedia-artikel dat overeenkomt met de naam van het avontuur.",
"wikipedia": "Wikipedia", "wikipedia": "Wikipedia",
"adventure_not_found": "Er zijn geen avonturen om weer te geven. \nVoeg er een paar toe via de plusknop rechtsonder of probeer de filters te wijzigen!", "adventure_not_found": "Er zijn geen avonturen om weer te geven. \nVoeg er een paar toe via de plusknop rechtsonder of probeer de filters te wijzigen!",
"all": "Alle", "all": "Alle",
@ -124,7 +107,6 @@
"mark_visited": "Markeer als bezocht", "mark_visited": "Markeer als bezocht",
"my_adventures": "Mijn avonturen", "my_adventures": "Mijn avonturen",
"no_adventures_found": "Geen avonturen gevonden", "no_adventures_found": "Geen avonturen gevonden",
"no_collections_found": "Er zijn geen collecties gevonden waar dit avontuur aan kan worden toegevoegd.",
"no_linkable_adventures": "Er zijn geen avonturen gevonden die aan deze collectie kunnen worden gekoppeld.", "no_linkable_adventures": "Er zijn geen avonturen gevonden die aan deze collectie kunnen worden gekoppeld.",
"not_visited": "Niet bezocht", "not_visited": "Niet bezocht",
"regions_updated": "regio's bijgewerkt", "regions_updated": "regio's bijgewerkt",
@ -181,10 +163,7 @@
"to": "Naar", "to": "Naar",
"transportation_delete_confirm": "Weet u zeker dat u dit transport wilt verwijderen? \nDeze actie kan niet ongedaan worden gemaakt.", "transportation_delete_confirm": "Weet u zeker dat u dit transport wilt verwijderen? \nDeze actie kan niet ongedaan worden gemaakt.",
"ending_airport": "Luchthaven van aankomst", "ending_airport": "Luchthaven van aankomst",
"will_be_marked": "wordt gemarkeerd als bezocht zodra het avontuur is opgeslagen.",
"cities_updated": "steden bijgewerkt", "cities_updated": "steden bijgewerkt",
"create_adventure": "Creëer avontuur",
"no_adventures_to_recommendations": "Geen avonturen gevonden. \nVoeg ten minste één avontuur toe om aanbevelingen te krijgen.",
"finding_recommendations": "Ontdek verborgen juweeltjes voor je volgende avontuur", "finding_recommendations": "Ontdek verborgen juweeltjes voor je volgende avontuur",
"attachment": "Bijlage", "attachment": "Bijlage",
"attachment_delete_success": "Bijlage succesvol verwijderd!", "attachment_delete_success": "Bijlage succesvol verwijderd!",
@ -246,7 +225,32 @@
"name_location": "naam, locatie", "name_location": "naam, locatie",
"collection_contents": "Collectie-inhoud", "collection_contents": "Collectie-inhoud",
"check_in": "Inchecken", "check_in": "Inchecken",
"check_out": "Uitchecken" "check_out": "Uitchecken",
"collection_link_location_error": "Foutkoppelingslocatie naar verzameling",
"collection_link_location_success": "Locatie gekoppeld aan het succesvol verzamelen!",
"collection_locations": "Neem verzamellocaties op",
"collection_remove_location_error": "Fout het verwijderen van locatie uit het verzamelen",
"collection_remove_location_success": "Locatie verwijderd uit de collectie succesvol!",
"create_location": "Locatie maken",
"delete_location": "Verwijder locatie",
"edit_location": "Locatie bewerken",
"location_create_error": "Kan locatie niet maken",
"location_created": "Locatie gemaakt",
"location_delete_confirm": "Weet u zeker dat u deze locatie wilt verwijderen? \nDeze actie kan niet ongedaan worden gemaakt.",
"location_delete_success": "Locatie verwijderd met succes!",
"location_not_found": "Locatie niet gevonden",
"location_not_found_desc": "De locatie waarnaar u op zoek was, kon niet worden gevonden. \nProbeer een andere locatie of kom later terug.",
"location_update_error": "De locatie niet bijwerken",
"location_updated": "Locatie bijgewerkt",
"new_location": "Nieuwe locatie",
"no_collections_to_add_location": "Geen collecties gevonden om deze locatie toe te voegen aan.",
"no_locations_to_recommendations": "Geen locaties gevonden. \nVoeg minstens één locatie toe om aanbevelingen te krijgen.",
"public_location": "Openbare locatie",
"share_location": "Deel deze locatie!",
"visit_calendar": "Bezoek de agenda",
"wiki_location_desc": "Haalt fragment uit het Wikipedia -artikel dat overeenkomt met de naam van de locatie.",
"will_be_marked_location": "wordt gemarkeerd als bezocht zodra de locatie is opgeslagen.",
"no_locations_found": "Geen locaties gevonden"
}, },
"home": { "home": {
"desc_1": "Ontdek, plan en verken met gemak", "desc_1": "Ontdek, plan en verken met gemak",
@ -317,10 +321,10 @@
"public_tooltip": "Met een openbaar profiel kunnen gebruikers collecties met u delen en uw profiel bekijken op de gebruikerspagina.", "public_tooltip": "Met een openbaar profiel kunnen gebruikers collecties met u delen en uw profiel bekijken op de gebruikerspagina.",
"new_password": "Nieuw wachtwoord", "new_password": "Nieuw wachtwoord",
"or_3rd_party": "Of log in met een service van derden", "or_3rd_party": "Of log in met een service van derden",
"no_public_adventures": "Geen openbare avonturen gevonden",
"no_public_collections": "Geen openbare collecties gevonden", "no_public_collections": "Geen openbare collecties gevonden",
"user_adventures": "Gebruikersavonturen", "user_collections": "Gebruikerscollecties",
"user_collections": "Gebruikerscollecties" "no_public_locations": "Geen openbare locaties gevonden",
"user_locations": "Gebruikerslocaties"
}, },
"users": { "users": {
"no_users_found": "Er zijn geen gebruikers gevonden met openbare profielen." "no_users_found": "Er zijn geen gebruikers gevonden met openbare profielen."
@ -568,7 +572,13 @@
"search": { "search": {
"adventurelog_results": "AdventureLog resultaten", "adventurelog_results": "AdventureLog resultaten",
"online_results": "Online resultaten", "online_results": "Online resultaten",
"public_adventures": "Openbare avonturen" "public_adventures": "Openbare avonturen",
"cities": "Steden",
"countries": "Landen",
"found": "gevonden",
"result": "Resultaat",
"results": "Resultaat",
"try_searching_desc": "Probeer op zoek naar avonturen, collecties, landen, regio's, steden of gebruikers."
}, },
"map": { "map": {
"add_adventure": "Voeg nieuw avontuur toe", "add_adventure": "Voeg nieuw avontuur toe",
@ -579,13 +589,16 @@
"show_visited_regions": "Toon bezochte regio's", "show_visited_regions": "Toon bezochte regio's",
"view_details": "Details bekijken", "view_details": "Details bekijken",
"adventure_stats": "Avontuurstatistieken", "adventure_stats": "Avontuurstatistieken",
"adventures_shown": "Avonturen getoond",
"completion": "Voltooiing", "completion": "Voltooiing",
"display_options": "Displayopties", "display_options": "Displayopties",
"map_controls": "Kaartbesturing", "map_controls": "Kaartbesturing",
"marker_placed_on_map": "Marker geplaatst op kaart", "marker_placed_on_map": "Marker geplaatst op kaart",
"place_marker_desc": "Klik op de kaart om een marker te plaatsen of voeg een avontuur toe zonder locatie.", "regions": "Gebieden",
"regions": "Gebieden" "add_location": "Voeg een nieuwe locatie toe",
"add_location_at_marker": "Voeg een nieuwe locatie toe bij Marker",
"location_map": "Locatiekaart",
"locations_shown": "Getoonde locaties",
"place_marker_desc_location": "Klik op de kaart om een marker te plaatsen."
}, },
"languages": {}, "languages": {},
"share": { "share": {
@ -611,9 +624,9 @@
"no_shared_adventures": "Deze gebruiker heeft nog geen openbare avonturen gedeeld.", "no_shared_adventures": "Deze gebruiker heeft nog geen openbare avonturen gedeeld.",
"no_shared_collections": "Deze gebruiker heeft nog geen openbare collecties gedeeld.", "no_shared_collections": "Deze gebruiker heeft nog geen openbare collecties gedeeld.",
"planned_trips": "Geplande reizen", "planned_trips": "Geplande reizen",
"public_adventure_experiences": "Publieke avontuurlijke ervaringen",
"travel_statistics": "Reisstatistieken", "travel_statistics": "Reisstatistieken",
"your_journey_at_a_glance": "Je avontuurlijke reis in één oogopslag" "your_journey_at_a_glance": "Je avontuurlijke reis in één oogopslag",
"public_location_experiences": "Openbare locatie -ervaringen"
}, },
"categories": { "categories": {
"category_name": "Categorienaam", "category_name": "Categorienaam",
@ -622,9 +635,9 @@
"manage_categories": "Beheer categorieën", "manage_categories": "Beheer categorieën",
"no_categories_found": "Geen categorieën gevonden.", "no_categories_found": "Geen categorieën gevonden.",
"select_category": "Selecteer een categorie", "select_category": "Selecteer een categorie",
"update_after_refresh": "De avonturenkaarten worden bijgewerkt zodra u de pagina vernieuwt.",
"add_new_category": "Voeg een nieuwe categorie toe", "add_new_category": "Voeg een nieuwe categorie toe",
"name_required": "Categorienaam is vereist" "name_required": "Categorienaam is vereist",
"location_update_after_refresh": "De locatiekaarten worden bijgewerkt zodra u de pagina vernieuwt."
}, },
"dashboard": { "dashboard": {
"add_some": "Waarom begint u niet met het plannen van uw volgende avontuur? \nJe kunt een nieuw avontuur toevoegen door op de onderstaande knop te klikken.", "add_some": "Waarom begint u niet met het plannen van uw volgende avontuur? \nJe kunt een nieuw avontuur toevoegen door op de onderstaande knop te klikken.",
@ -670,9 +683,9 @@
"recomendations": { "recomendations": {
"recommendation": "Aanbeveling", "recommendation": "Aanbeveling",
"recommendations": "Aanbevelingen", "recommendations": "Aanbevelingen",
"adventure_recommendations": "Avontuuraanbevelingen", "food": "Voedsel",
"food": "Eten", "tourism": "Toerisme",
"tourism": "Toerisme" "location_recommendations": "Locatieaanbevelingen"
}, },
"lodging": { "lodging": {
"apartment": "Appartement", "apartment": "Appartement",
@ -695,17 +708,19 @@
"google_maps_integration_desc": "Sluit uw Google Maps-account aan om zoekresultaten en aanbevelingen van hoge kwaliteit te krijgen." "google_maps_integration_desc": "Sluit uw Google Maps-account aan om zoekresultaten en aanbevelingen van hoge kwaliteit te krijgen."
}, },
"calendar": { "calendar": {
"all_categories": "Alle categorieën", "all_day_event": "De hele dag evenement",
"all_day_event": "Evenement dat de hele dag duurt",
"calendar_overview": "Kalenderoverzicht", "calendar_overview": "Kalenderoverzicht",
"categories": "Categorieën",
"day": "Dag", "day": "Dag",
"events_scheduled": "geplande evenementen", "events_scheduled": "geplande evenementen",
"filter_by_category": "Filter per categorie",
"filtered_results": "Gefilterde resultaten", "filtered_results": "Gefilterde resultaten",
"month": "Maand", "month": "Maand",
"today": "Vandaag", "today": "Vandaag",
"total_events": "Totale gebeurtenissen", "total_events": "Totale gebeurtenissen",
"week": "Week" "week": "Week"
},
"locations": {
"location": "Locatie",
"locations": "Locaties",
"my_locations": "Mijn locaties"
} }
} }

View file

@ -64,12 +64,7 @@
"start_your_journey": "Start reisen" "start_your_journey": "Start reisen"
}, },
"adventures": { "adventures": {
"collection_remove_success": "Eventyret ble fjernet fra samlingen!",
"collection_remove_error": "Feil ved fjerning av eventyr fra samling",
"collection_link_success": "Eventyret ble lagt til samlingen!",
"no_image_found": "Ingen bilde funnet", "no_image_found": "Ingen bilde funnet",
"collection_link_error": "Feil ved lenking av eventyr til samling",
"adventure_delete_confirm": "Er du sikker på at du vil slette dette eventyret? Denne handlingen kan ikke angres.",
"checklist_delete_confirm": "Er du sikker på at du vil slette denne sjekklisten? Denne handlingen kan ikke angres.", "checklist_delete_confirm": "Er du sikker på at du vil slette denne sjekklisten? Denne handlingen kan ikke angres.",
"note_delete_confirm": "Er du sikker på at du vil slette dette notatet? Denne handlingen kan ikke angres.", "note_delete_confirm": "Er du sikker på at du vil slette dette notatet? Denne handlingen kan ikke angres.",
"transportation_delete_confirm": "Er du sikker på at du vil slette dette transportmiddelet? Denne handlingen kan ikke angres.", "transportation_delete_confirm": "Er du sikker på at du vil slette dette transportmiddelet? Denne handlingen kan ikke angres.",
@ -83,8 +78,6 @@
"remove_from_collection": "Fjern fra samling", "remove_from_collection": "Fjern fra samling",
"add_to_collection": "Legg til i samling", "add_to_collection": "Legg til i samling",
"delete": "Slett", "delete": "Slett",
"not_found": "Fant ikke eventyret",
"not_found_desc": "Eventyret du leter etter, ble ikke funnet. Vennligst prøv et annet eventyr eller kom tilbake senere.",
"homepage": "Hjemmeside", "homepage": "Hjemmeside",
"collection": "Samling", "collection": "Samling",
"longitude": "Lengdegrad", "longitude": "Lengdegrad",
@ -109,7 +102,6 @@
"rating": "Vurdering", "rating": "Vurdering",
"my_images": "Mine bilder", "my_images": "Mine bilder",
"no_images": "Ingen bilder", "no_images": "Ingen bilder",
"share_adventure": "Del dette eventyret!",
"copy_link": "Kopier lenke", "copy_link": "Kopier lenke",
"image": "Bilde", "image": "Bilde",
"upload_image": "Last opp bilde", "upload_image": "Last opp bilde",
@ -131,12 +123,10 @@
"clear_map": "Tøm kart", "clear_map": "Tøm kart",
"search_results": "Søkeresultater", "search_results": "Søkeresultater",
"no_results": "Ingen resultater funnet", "no_results": "Ingen resultater funnet",
"wiki_desc": "Henter utdrag fra Wikipedia-artikkelen som samsvarer med navnet på eventyret.",
"attachments": "Vedlegg", "attachments": "Vedlegg",
"attachment": "Vedlegg", "attachment": "Vedlegg",
"images": "Bilder", "images": "Bilder",
"generate_desc": "Generer beskrivelse", "generate_desc": "Generer beskrivelse",
"public_adventure": "Offentlig eventyr",
"location_information": "Plasseringsinformasjon", "location_information": "Plasseringsinformasjon",
"link": "Lenke", "link": "Lenke",
"links": "Lenker", "links": "Lenker",
@ -157,15 +147,12 @@
"edit_collection": "Rediger samling", "edit_collection": "Rediger samling",
"unarchive": "Fjern fra arkiv", "unarchive": "Fjern fra arkiv",
"archive": "Arkiver", "archive": "Arkiver",
"no_collections_found": "Ingen samlinger funnet for å legge dette eventyret til.",
"not_visited": "Ikke besøkt", "not_visited": "Ikke besøkt",
"archived_collection_message": "Samlingen ble arkivert!", "archived_collection_message": "Samlingen ble arkivert!",
"unarchived_collection_message": "Samlingen ble fjernet fra arkivet!", "unarchived_collection_message": "Samlingen ble fjernet fra arkivet!",
"delete_collection_success": "Samlingen ble slettet!", "delete_collection_success": "Samlingen ble slettet!",
"cancel": "Avbryt", "cancel": "Avbryt",
"delete_collection": "Slett samling", "delete_collection": "Slett samling",
"delete_adventure": "Slett eventyr",
"adventure_delete_success": "Eventyret ble slettet!",
"visited": "Besøkt", "visited": "Besøkt",
"planned": "Planlagt", "planned": "Planlagt",
"duration": "Varighet", "duration": "Varighet",
@ -183,17 +170,10 @@
"image_fetch_failed": "Kunne ikke hente bilde", "image_fetch_failed": "Kunne ikke hente bilde",
"no_location": "Vennligst angi et sted", "no_location": "Vennligst angi et sted",
"no_description_found": "Fant ingen beskrivelse", "no_description_found": "Fant ingen beskrivelse",
"adventure_created": "Eventyr opprettet",
"adventure_create_error": "Kunne ikke opprette eventyr",
"lodging": "Overnatting", "lodging": "Overnatting",
"create_adventure": "Opprett eventyr",
"adventure_updated": "Eventyr oppdatert",
"adventure_update_error": "Kunne ikke oppdatere eventyr",
"set_to_pin": "Fest", "set_to_pin": "Fest",
"category_fetch_error": "Feil ved henting av kategorier", "category_fetch_error": "Feil ved henting av kategorier",
"new_adventure": "Nytt eventyr",
"basic_information": "Grunnleggende informasjon", "basic_information": "Grunnleggende informasjon",
"no_adventures_to_recommendations": "Ingen eventyr funnet. Legg til minst ett eventyr for å få anbefalinger.",
"display_name": "Visningsnavn", "display_name": "Visningsnavn",
"adventure_not_found": "Det finnes ingen eventyr å vise. Legg til noen ved å trykke på plusstegnet nederst til høyre, eller prøv å endre filtre!", "adventure_not_found": "Det finnes ingen eventyr å vise. Legg til noen ved å trykke på plusstegnet nederst til høyre, eller prøv å endre filtre!",
"no_adventures_found": "Ingen eventyr funnet", "no_adventures_found": "Ingen eventyr funnet",
@ -233,7 +213,6 @@
"no_location_found": "Ingen sted funnet", "no_location_found": "Ingen sted funnet",
"from": "Fra", "from": "Fra",
"to": "Til", "to": "Til",
"will_be_marked": "vil bli markert som besøkt når eventyret er lagret.",
"start": "Start", "start": "Start",
"end": "Slutt", "end": "Slutt",
"emoji_picker": "Emoji-velger", "emoji_picker": "Emoji-velger",
@ -298,7 +277,32 @@
"name_location": "Navn, plassering", "name_location": "Navn, plassering",
"collection_contents": "Samlingsinnhold", "collection_contents": "Samlingsinnhold",
"check_in": "Sjekk inn", "check_in": "Sjekk inn",
"check_out": "Sjekk ut" "check_out": "Sjekk ut",
"collection_link_location_error": "Feil koblingssted til samling",
"collection_link_location_success": "Plassering knyttet til samlingen vellykket!",
"collection_locations": "Inkluderer samlingssteder",
"collection_remove_location_error": "Feil å fjerne plasseringen fra samlingen",
"collection_remove_location_success": "Plassering fjernet fra samlingen med hell!",
"create_location": "Skape sted",
"delete_location": "Slett plassering",
"edit_location": "Rediger plassering",
"location_create_error": "Kunne ikke skape sted",
"location_created": "Plassering opprettet",
"location_delete_confirm": "Er du sikker på at du vil slette dette stedet? \nDenne handlingen kan ikke angres.",
"location_delete_success": "Plassering slettet vellykket!",
"location_not_found": "Plasseringen ikke funnet",
"location_not_found_desc": "Plasseringen du lette etter ble ikke funnet. \nPrøv et annet sted eller sjekk tilbake senere.",
"location_update_error": "Kunne ikke oppdatere plasseringen",
"location_updated": "Plassering oppdatert",
"new_location": "Ny beliggenhet",
"no_collections_to_add_location": "Ingen samlinger funnet å legge dette stedet til.",
"no_locations_to_recommendations": "Ingen steder funnet. \nLegg til minst ett sted for å få anbefalinger.",
"public_location": "Offentlig beliggenhet",
"share_location": "Del dette stedet!",
"visit_calendar": "Besøk kalenderen",
"wiki_location_desc": "Trekker utdrag fra Wikipedia -artikkelen som samsvarer med navnet på stedet.",
"will_be_marked_location": "vil bli merket som besøkt når stedet er lagret.",
"no_locations_found": "Ingen steder funnet"
}, },
"worldtravel": { "worldtravel": {
"country_list": "Liste over land", "country_list": "Liste over land",
@ -373,10 +377,10 @@
"public_tooltip": "Med en offentlig profil kan brukere dele samlinger med deg og se profilen din på brukersiden.", "public_tooltip": "Med en offentlig profil kan brukere dele samlinger med deg og se profilen din på brukersiden.",
"new_password": "Nytt passord (6+ tegn)", "new_password": "Nytt passord (6+ tegn)",
"or_3rd_party": "Eller logg inn med en tredjepartstjeneste", "or_3rd_party": "Eller logg inn med en tredjepartstjeneste",
"no_public_adventures": "Ingen offentlige eventyr funnet",
"no_public_collections": "Ingen offentlige samlinger funnet", "no_public_collections": "Ingen offentlige samlinger funnet",
"user_adventures": "Brukerens eventyr", "user_collections": "Brukerens samlinger",
"user_collections": "Brukerens samlinger" "no_public_locations": "Ingen offentlige steder funnet",
"user_locations": "Brukerplasser"
}, },
"users": { "users": {
"no_users_found": "Ingen brukere med offentlig profil funnet." "no_users_found": "Ingen brukere med offentlig profil funnet."
@ -585,7 +589,13 @@
"search": { "search": {
"adventurelog_results": "AdventureLog-resultater", "adventurelog_results": "AdventureLog-resultater",
"public_adventures": "Offentlige eventyr", "public_adventures": "Offentlige eventyr",
"online_results": "Nettresultater" "online_results": "Nettresultater",
"cities": "Byer",
"countries": "Land",
"found": "funnet",
"result": "Resultat",
"results": "Resultater",
"try_searching_desc": "Prøv å søke etter eventyr, samlinger, land, regioner, byer eller brukere."
}, },
"map": { "map": {
"view_details": "Vis detaljer", "view_details": "Vis detaljer",
@ -596,13 +606,16 @@
"clear_marker": "Fjern markør", "clear_marker": "Fjern markør",
"add_adventure": "Legg til nytt eventyr", "add_adventure": "Legg til nytt eventyr",
"adventure_stats": "Eventyrstatistikk", "adventure_stats": "Eventyrstatistikk",
"adventures_shown": "Eventyr vist",
"completion": "Fullføring", "completion": "Fullføring",
"display_options": "Vis alternativer", "display_options": "Vis alternativer",
"map_controls": "Kartkontroller", "map_controls": "Kartkontroller",
"marker_placed_on_map": "Markør plassert på kart", "marker_placed_on_map": "Markør plassert på kart",
"place_marker_desc": "Klikk på kartet for å plassere en markør, eller legg til et eventyr uten beliggenhet.", "regions": "Regioner",
"regions": "Regioner" "add_location": "Legg til nytt sted",
"add_location_at_marker": "Legg til nytt sted på markør",
"location_map": "Stedskart",
"locations_shown": "steder vist",
"place_marker_desc_location": "Klikk på kartet for å plassere en markør."
}, },
"share": { "share": {
"shared": "Delt", "shared": "Delt",
@ -628,20 +641,20 @@
"no_shared_adventures": "Denne brukeren har ikke delt noen offentlige eventyr ennå.", "no_shared_adventures": "Denne brukeren har ikke delt noen offentlige eventyr ennå.",
"no_shared_collections": "Denne brukeren har ikke delt noen offentlige samlinger ennå.", "no_shared_collections": "Denne brukeren har ikke delt noen offentlige samlinger ennå.",
"planned_trips": "Planlagte turer", "planned_trips": "Planlagte turer",
"public_adventure_experiences": "Offentlige eventyropplevelser",
"travel_statistics": "Reisestatistikk", "travel_statistics": "Reisestatistikk",
"your_journey_at_a_glance": "Din eventyrreise på et øyeblikk" "your_journey_at_a_glance": "Din eventyrreise på et øyeblikk",
"public_location_experiences": "Offentlige beliggenhetsopplevelser"
}, },
"categories": { "categories": {
"manage_categories": "Administrer kategorier", "manage_categories": "Administrer kategorier",
"no_categories_found": "Ingen kategorier funnet.", "no_categories_found": "Ingen kategorier funnet.",
"edit_category": "Rediger kategori", "edit_category": "Rediger kategori",
"icon": "Ikon", "icon": "Ikon",
"update_after_refresh": "Eventyrkortene vil oppdateres når du oppdaterer siden.",
"select_category": "Velg kategori", "select_category": "Velg kategori",
"category_name": "Kategorinavn", "category_name": "Kategorinavn",
"add_new_category": "Legg til ny kategori", "add_new_category": "Legg til ny kategori",
"name_required": "Kategorinavnet er påkrevd" "name_required": "Kategorinavnet er påkrevd",
"location_update_after_refresh": "Stedskortene vil bli oppdatert når du oppdaterer siden."
}, },
"dashboard": { "dashboard": {
"welcome_back": "Velkommen tilbake", "welcome_back": "Velkommen tilbake",
@ -687,25 +700,27 @@
"recomendations": { "recomendations": {
"recommendation": "Anbefaling", "recommendation": "Anbefaling",
"recommendations": "Anbefalinger", "recommendations": "Anbefalinger",
"adventure_recommendations": "Eventyranbefalinger",
"food": "Mat", "food": "Mat",
"tourism": "Turisme" "tourism": "Turisme",
"location_recommendations": "Stedsanbefalinger"
}, },
"google_maps": { "google_maps": {
"google_maps_integration_desc": "Koble til Google Maps-kontoen din for å få søkeresultater og anbefalinger av høy kvalitet." "google_maps_integration_desc": "Koble til Google Maps-kontoen din for å få søkeresultater og anbefalinger av høy kvalitet."
}, },
"calendar": { "calendar": {
"all_categories": "Alle kategorier",
"all_day_event": "Hele dagens arrangement", "all_day_event": "Hele dagens arrangement",
"calendar_overview": "Kalenderoversikt", "calendar_overview": "Kalenderoversikt",
"categories": "Kategorier",
"day": "Dag", "day": "Dag",
"events_scheduled": "hendelser planlagt", "events_scheduled": "hendelser planlagt",
"filter_by_category": "Filter etter kategori",
"filtered_results": "Filtrerte resultater", "filtered_results": "Filtrerte resultater",
"month": "Måned", "month": "Måned",
"today": "I dag", "today": "I dag",
"total_events": "Total hendelser", "total_events": "Total hendelser",
"week": "Uke" "week": "Uke"
},
"locations": {
"location": "Sted",
"locations": "Lokasjoner",
"my_locations": "Mine lokasjoner"
} }
} }

View file

@ -64,19 +64,12 @@
"start_your_journey": "Rozpocznij swoją podróż" "start_your_journey": "Rozpocznij swoją podróż"
}, },
"adventures": { "adventures": {
"collection_remove_success": "Podróż została pomyślnie usunięta z kolekcji!",
"collection_remove_error": "Błąd podczas usuwania podróży z kolekcji",
"collection_link_success": "Podróż została pomyślnie dodana do kolekcji!",
"no_image_found": "Nie znaleziono obrazu", "no_image_found": "Nie znaleziono obrazu",
"collection_link_error": "Błąd podczas dodawania podróży do kolekcji",
"adventure_delete_confirm": "Czy na pewno chcesz usunąć tę podróż? Ta operacja jest nieodwracalna.",
"open_details": "Otwórz szczegóły", "open_details": "Otwórz szczegóły",
"edit_adventure": "Edytuj podróż", "edit_adventure": "Edytuj podróż",
"remove_from_collection": "Usuń z kolekcji", "remove_from_collection": "Usuń z kolekcji",
"add_to_collection": "Dodaj do kolekcji", "add_to_collection": "Dodaj do kolekcji",
"delete": "Usuń", "delete": "Usuń",
"not_found": "Podróż nie znaleziona",
"not_found_desc": "Podróży, której szukasz, nie można znaleźć. Spróbuj poszukać innej podróży lub sprawdź później.",
"homepage": "Strona główna", "homepage": "Strona główna",
"collection": "Kolekcja", "collection": "Kolekcja",
"longitude": "Długość geograficzna", "longitude": "Długość geograficzna",
@ -101,7 +94,6 @@
"rating": "Ocena", "rating": "Ocena",
"my_images": "Moje obrazy", "my_images": "Moje obrazy",
"no_images": "Brak obrazów", "no_images": "Brak obrazów",
"share_adventure": "Podziel się tą podróżą!",
"copy_link": "Kopiuj link", "copy_link": "Kopiuj link",
"image": "Obraz", "image": "Obraz",
"upload_image": "Prześlij obraz", "upload_image": "Prześlij obraz",
@ -122,9 +114,7 @@
"clear_map": "Wyczyść mapę", "clear_map": "Wyczyść mapę",
"search_results": "Wyniki wyszukiwania", "search_results": "Wyniki wyszukiwania",
"no_results": "Nie znaleziono wyników", "no_results": "Nie znaleziono wyników",
"wiki_desc": "Pobiera fragment artykułu z Wikipedii pasującego do nazwy podróży.",
"generate_desc": "Generuj opis", "generate_desc": "Generuj opis",
"public_adventure": "Publiczna podróż",
"location_information": "Informacje o lokalizacji", "location_information": "Informacje o lokalizacji",
"link": "Link", "link": "Link",
"links": "Linki", "links": "Linki",
@ -145,15 +135,12 @@
"edit_collection": "Edytuj kolekcję", "edit_collection": "Edytuj kolekcję",
"unarchive": "Przywróć z archiwum", "unarchive": "Przywróć z archiwum",
"archive": "Archiwizuj", "archive": "Archiwizuj",
"no_collections_found": "Nie znaleziono kolekcji, do których można dodać tę podróż.",
"not_visited": "Nie odwiedzone", "not_visited": "Nie odwiedzone",
"archived_collection_message": "Kolekcja została pomyślnie zarchiwizowana!", "archived_collection_message": "Kolekcja została pomyślnie zarchiwizowana!",
"unarchived_collection_message": "Kolekcja została pomyślnie przywrócona z archiwum!", "unarchived_collection_message": "Kolekcja została pomyślnie przywrócona z archiwum!",
"delete_collection_success": "Kolekcja została pomyślnie usunięta!", "delete_collection_success": "Kolekcja została pomyślnie usunięta!",
"cancel": "Anuluj", "cancel": "Anuluj",
"delete_collection": "Usuń kolekcję", "delete_collection": "Usuń kolekcję",
"delete_adventure": "Usuń wyprawę",
"adventure_delete_success": "Podróż została pomyślnie usunięta!",
"visited": "Odwiedzona", "visited": "Odwiedzona",
"planned": "Planowana", "planned": "Planowana",
"duration": "Czas trwania", "duration": "Czas trwania",
@ -171,13 +158,8 @@
"image_fetch_failed": "Nie udało się pobrać obrazu", "image_fetch_failed": "Nie udało się pobrać obrazu",
"no_location": "Proszę podać lokalizację", "no_location": "Proszę podać lokalizację",
"no_description_found": "Nie znaleziono opisu", "no_description_found": "Nie znaleziono opisu",
"adventure_created": "Podróż została utworzona",
"adventure_create_error": "Nie udało się stworzyć podróży",
"adventure_updated": "Podróż została zaktualizowana",
"adventure_update_error": "Nie udało się zaktualizować podróży",
"set_to_pin": "Ustaw jako przypiętą", "set_to_pin": "Ustaw jako przypiętą",
"category_fetch_error": "Błąd podczas pobierania kategorii", "category_fetch_error": "Błąd podczas pobierania kategorii",
"new_adventure": "Nowa podróż",
"basic_information": "Podstawowe informacje", "basic_information": "Podstawowe informacje",
"adventure_not_found": "Brak podróży do wyświetlenia. Dodaj je za pomocą przycisku plus w prawym dolnym rogu lub spróbuj zmienić filtry!", "adventure_not_found": "Brak podróży do wyświetlenia. Dodaj je za pomocą przycisku plus w prawym dolnym rogu lub spróbuj zmienić filtry!",
"no_adventures_found": "Brak podróży", "no_adventures_found": "Brak podróży",
@ -233,10 +215,7 @@
"starting_airport": "Początkowe lotnisko", "starting_airport": "Początkowe lotnisko",
"to": "Do", "to": "Do",
"transportation_delete_confirm": "Czy na pewno chcesz usunąć ten transport? \nTej akcji nie można cofnąć.", "transportation_delete_confirm": "Czy na pewno chcesz usunąć ten transport? \nTej akcji nie można cofnąć.",
"will_be_marked": "zostanie oznaczona jako odwiedzona po zapisaniu przygody.",
"cities_updated": "miasta zaktualizowane", "cities_updated": "miasta zaktualizowane",
"create_adventure": "Stwórz przygodę",
"no_adventures_to_recommendations": "Nie znaleziono żadnych przygód. \nDodaj co najmniej jedną przygodę, aby uzyskać rekomendacje.",
"finding_recommendations": "Odkrywanie ukrytych klejnotów na następną przygodę", "finding_recommendations": "Odkrywanie ukrytych klejnotów na następną przygodę",
"attachment": "Załącznik", "attachment": "Załącznik",
"attachment_delete_success": "Załącznik został pomyślnie usunięty!", "attachment_delete_success": "Załącznik został pomyślnie usunięty!",
@ -298,7 +277,32 @@
"delete_collection_warning": "Czy na pewno chcesz usunąć tę kolekcję? \nTego działania nie można cofnąć.", "delete_collection_warning": "Czy na pewno chcesz usunąć tę kolekcję? \nTego działania nie można cofnąć.",
"collection_contents": "Zawartość kolekcji", "collection_contents": "Zawartość kolekcji",
"check_in": "Zameldować się", "check_in": "Zameldować się",
"check_out": "Wymeldować się" "check_out": "Wymeldować się",
"collection_link_location_error": "Błąd łączący lokalizację z kolekcją",
"collection_link_location_success": "Lokalizacja powiązana z kolekcją pomyślnie!",
"collection_locations": "Obejmują lokalizacje kolekcji",
"collection_remove_location_error": "Lokalizacja usuwania błędów z kolekcji",
"collection_remove_location_success": "Lokalizacja pomyślnie usunięta z kolekcji!",
"create_location": "Utwórz lokalizację",
"delete_location": "Usuń lokalizację",
"edit_location": "Edytuj lokalizację",
"location_create_error": "Nie udało się utworzyć lokalizacji",
"location_created": "Utworzona lokalizacja",
"location_delete_confirm": "Czy na pewno chcesz usunąć tę lokalizację? \nTego działania nie można cofnąć.",
"location_delete_success": "Lokalizacja pomyślnie usunięta!",
"location_not_found": "Nie znaleziono lokalizacji",
"location_not_found_desc": "Nie można było znaleźć lokalizacji. \nWypróbuj inną lokalizację lub sprawdź później.",
"location_update_error": "Nie udało się zaktualizować lokalizacji",
"location_updated": "Zaktualizowana lokalizacja",
"new_location": "Nowa lokalizacja",
"no_collections_to_add_location": "Brak kolekcji dodawania tej lokalizacji do.",
"no_locations_to_recommendations": "Nie znaleziono żadnych lokalizacji. \nDodaj co najmniej jedną lokalizację, aby uzyskać zalecenia.",
"public_location": "Lokalizacja publiczna",
"share_location": "Udostępnij tę lokalizację!",
"visit_calendar": "Odwiedź kalendarz",
"wiki_location_desc": "Wyciąga fragment artykułu Wikipedii pasujący do nazwy lokalizacji.",
"will_be_marked_location": "zostanie oznaczone jako odwiedzone po zapisaniu lokalizacji.",
"no_locations_found": "Nie znaleziono żadnych lokalizacji"
}, },
"worldtravel": { "worldtravel": {
"country_list": "Lista krajów", "country_list": "Lista krajów",
@ -373,10 +377,10 @@
"public_tooltip": "Dzięki publicznemu profilowi użytkownicy mogą dzielić się z Tobą kolekcjami i oglądać Twój profil na stronie użytkowników.", "public_tooltip": "Dzięki publicznemu profilowi użytkownicy mogą dzielić się z Tobą kolekcjami i oglądać Twój profil na stronie użytkowników.",
"new_password": "Nowe hasło", "new_password": "Nowe hasło",
"or_3rd_party": "Lub zaloguj się za pomocą usługi strony trzeciej", "or_3rd_party": "Lub zaloguj się za pomocą usługi strony trzeciej",
"no_public_adventures": "Nie znaleziono publicznych przygód",
"no_public_collections": "Nie znaleziono publicznych kolekcji", "no_public_collections": "Nie znaleziono publicznych kolekcji",
"user_adventures": "Przygody użytkowników", "user_collections": "Kolekcje użytkowników",
"user_collections": "Kolekcje użytkowników" "no_public_locations": "Nie znaleziono żadnych lokalizacji publicznych",
"user_locations": "Lokalizacje użytkowników"
}, },
"users": { "users": {
"no_users_found": "Nie znaleziono użytkowników z publicznymi profilami." "no_users_found": "Nie znaleziono użytkowników z publicznymi profilami."
@ -568,7 +572,13 @@
"search": { "search": {
"adventurelog_results": "Wyniki AdventureLog", "adventurelog_results": "Wyniki AdventureLog",
"public_adventures": "Publiczne podróże", "public_adventures": "Publiczne podróże",
"online_results": "Wyniki online" "online_results": "Wyniki online",
"cities": "Miasta",
"countries": "Kraje",
"found": "znaleziony",
"result": "Wynik",
"results": "Wyniki",
"try_searching_desc": "Spróbuj szukać przygód, kolekcji, krajów, regionów, miast lub użytkowników."
}, },
"map": { "map": {
"view_details": "Zobacz szczegóły", "view_details": "Zobacz szczegóły",
@ -579,13 +589,16 @@
"clear_marker": "Usuń znacznik", "clear_marker": "Usuń znacznik",
"add_adventure": "Dodaj nową podróż", "add_adventure": "Dodaj nową podróż",
"adventure_stats": "Statystyki przygodowe", "adventure_stats": "Statystyki przygodowe",
"adventures_shown": "Pokazane przygody",
"completion": "Ukończenie", "completion": "Ukończenie",
"display_options": "Opcje wyświetlania", "display_options": "Opcje wyświetlania",
"map_controls": "Sterowanie mapą", "map_controls": "Sterowanie mapą",
"marker_placed_on_map": "Marker umieszczony na mapie", "marker_placed_on_map": "Marker umieszczony na mapie",
"place_marker_desc": "Kliknij mapę, aby umieścić znacznik lub dodać przygodę bez lokalizacji.", "regions": "Regiony",
"regions": "Regiony" "add_location": "Dodaj nową lokalizację",
"add_location_at_marker": "Dodaj nową lokalizację na znaczniku",
"location_map": "Mapa lokalizacji",
"locations_shown": "Pokazane lokalizacje",
"place_marker_desc_location": "Kliknij mapę, aby umieścić znacznik."
}, },
"share": { "share": {
"shared": "Udostępnione", "shared": "Udostępnione",
@ -611,20 +624,20 @@
"no_shared_adventures": "Ten użytkownik nie podzielił się jeszcze żadnymi publicznymi przygodami.", "no_shared_adventures": "Ten użytkownik nie podzielił się jeszcze żadnymi publicznymi przygodami.",
"no_shared_collections": "Ten użytkownik nie udostępnił jeszcze żadnych publicznych kolekcji.", "no_shared_collections": "Ten użytkownik nie udostępnił jeszcze żadnych publicznych kolekcji.",
"planned_trips": "Planowane wycieczki", "planned_trips": "Planowane wycieczki",
"public_adventure_experiences": "Public Adventure Doświadczenia",
"travel_statistics": "Statystyka podróży", "travel_statistics": "Statystyka podróży",
"your_journey_at_a_glance": "Twoja przygodowa podróż na pierwszy rzut oka" "your_journey_at_a_glance": "Twoja przygodowa podróż na pierwszy rzut oka",
"public_location_experiences": "Doświadczenia lokalizacji publicznej"
}, },
"categories": { "categories": {
"manage_categories": "Zarządzaj kategoriami", "manage_categories": "Zarządzaj kategoriami",
"no_categories_found": "Brak kategorii.", "no_categories_found": "Brak kategorii.",
"edit_category": "Edytuj kategorię", "edit_category": "Edytuj kategorię",
"icon": "Ikona", "icon": "Ikona",
"update_after_refresh": "Karty podróży zostaną zaktualizowane po odświeżeniu strony.",
"select_category": "Wybierz kategorię", "select_category": "Wybierz kategorię",
"category_name": "Nazwa kategorii", "category_name": "Nazwa kategorii",
"add_new_category": "Dodaj nową kategorię", "add_new_category": "Dodaj nową kategorię",
"name_required": "Nazwa kategorii jest wymagana" "name_required": "Nazwa kategorii jest wymagana",
"location_update_after_refresh": "Karty lokalizacji zostaną zaktualizowane po odświeżeniu strony."
}, },
"dashboard": { "dashboard": {
"add_some": "Dlaczego nie zacząć planować kolejnej przygody? \nMożesz dodać nową przygodę, klikając przycisk poniżej.", "add_some": "Dlaczego nie zacząć planować kolejnej przygody? \nMożesz dodać nową przygodę, klikając przycisk poniżej.",
@ -670,9 +683,9 @@
"recomendations": { "recomendations": {
"recommendation": "Zalecenie", "recommendation": "Zalecenie",
"recommendations": "Zalecenia", "recommendations": "Zalecenia",
"adventure_recommendations": "Zalecenia przygodowe",
"food": "Żywność", "food": "Żywność",
"tourism": "Turystyka" "tourism": "Turystyka",
"location_recommendations": "Zalecenia dotyczące lokalizacji"
}, },
"lodging": { "lodging": {
"apartment": "Apartament", "apartment": "Apartament",
@ -695,17 +708,19 @@
"google_maps_integration_desc": "Połącz swoje konto Google Maps, aby uzyskać wysokiej jakości wyniki wyszukiwania i zalecenia dotyczące lokalizacji." "google_maps_integration_desc": "Połącz swoje konto Google Maps, aby uzyskać wysokiej jakości wyniki wyszukiwania i zalecenia dotyczące lokalizacji."
}, },
"calendar": { "calendar": {
"all_categories": "Wszystkie kategorie",
"all_day_event": "Wydarzenie przez cały dzień", "all_day_event": "Wydarzenie przez cały dzień",
"calendar_overview": "Przegląd kalendarza", "calendar_overview": "Przegląd kalendarza",
"categories": "Kategorie",
"day": "Dzień", "day": "Dzień",
"events_scheduled": "Zaplanowane wydarzenia", "events_scheduled": "Zaplanowane wydarzenia",
"filter_by_category": "Filtr według kategorii",
"filtered_results": "Przefiltrowane wyniki", "filtered_results": "Przefiltrowane wyniki",
"month": "Miesiąc", "month": "Miesiąc",
"today": "Dzisiaj", "today": "Dzisiaj",
"total_events": "Całkowite zdarzenia", "total_events": "Całkowite zdarzenia",
"week": "Tydzień" "week": "Tydzień"
},
"locations": {
"location": "Lokalizacja",
"locations": "Lokalizacje",
"my_locations": "Moje lokalizacje"
} }
} }

View file

@ -64,9 +64,6 @@
"start_your_journey": "Начните свое путешествие" "start_your_journey": "Начните свое путешествие"
}, },
"adventures": { "adventures": {
"collection_remove_success": "Приключение успешно удалено из коллекции!",
"collection_remove_error": "Ошибка удаления приключения из коллекции",
"collection_link_success": "Приключение успешно связано с коллекцией!",
"invalid_date_range": "Недопустимый диапазон дат", "invalid_date_range": "Недопустимый диапазон дат",
"timezone": "Часовой пояс", "timezone": "Часовой пояс",
"no_visits": "Нет посещений", "no_visits": "Нет посещений",
@ -75,8 +72,6 @@
"departure_date": "Дата отправления", "departure_date": "Дата отправления",
"arrival_date": "Дата прибытия", "arrival_date": "Дата прибытия",
"no_image_found": "Изображение не найдено", "no_image_found": "Изображение не найдено",
"collection_link_error": "Ошибка связывания приключения с коллекцией",
"adventure_delete_confirm": "Вы уверены, что хотите удалить это приключение? Это действие нельзя отменить.",
"checklist_delete_confirm": "Вы уверены, что хотите удалить этот контрольный список? Это действие нельзя отменить.", "checklist_delete_confirm": "Вы уверены, что хотите удалить этот контрольный список? Это действие нельзя отменить.",
"note_delete_confirm": "Вы уверены, что хотите удалить эту заметку? Это действие нельзя отменить.", "note_delete_confirm": "Вы уверены, что хотите удалить эту заметку? Это действие нельзя отменить.",
"transportation_delete_confirm": "Вы уверены, что хотите удалить этот транспорт? Это действие нельзя отменить.", "transportation_delete_confirm": "Вы уверены, что хотите удалить этот транспорт? Это действие нельзя отменить.",
@ -90,8 +85,6 @@
"remove_from_collection": "Убрать из коллекции", "remove_from_collection": "Убрать из коллекции",
"add_to_collection": "Добавить в коллекцию", "add_to_collection": "Добавить в коллекцию",
"delete": "Удалить", "delete": "Удалить",
"not_found": "Приключение не найдено",
"not_found_desc": "Приключение, которое вы искали, не найдено. Попробуйте другое приключение или проверьте позже.",
"homepage": "Главная страница", "homepage": "Главная страница",
"collection": "Коллекция", "collection": "Коллекция",
"longitude": "Долгота", "longitude": "Долгота",
@ -120,7 +113,6 @@
"my_images": "Мои изображения", "my_images": "Мои изображения",
"no_images": "Нет изображений", "no_images": "Нет изображений",
"distance": "Расстояние", "distance": "Расстояние",
"share_adventure": "Поделиться этим приключением!",
"copy_link": "Копировать ссылку", "copy_link": "Копировать ссылку",
"sun_times": "Время солнца", "sun_times": "Время солнца",
"sunrise": "Восход", "sunrise": "Восход",
@ -146,12 +138,10 @@
"search_results": "Результаты поиска", "search_results": "Результаты поиска",
"collection_no_start_end_date": "Добавление дат начала и окончания коллекции разблокирует функции планирования маршрута на странице коллекции.", "collection_no_start_end_date": "Добавление дат начала и окончания коллекции разблокирует функции планирования маршрута на странице коллекции.",
"no_results": "Результаты не найдены", "no_results": "Результаты не найдены",
"wiki_desc": "Извлекает отрывок из статьи Википедии, соответствующей названию приключения.",
"attachments": "Вложения", "attachments": "Вложения",
"attachment": "Вложение", "attachment": "Вложение",
"images": "Изображения", "images": "Изображения",
"generate_desc": "Сгенерировать описание", "generate_desc": "Сгенерировать описание",
"public_adventure": "Публичное приключение",
"location_information": "Информация о местоположении", "location_information": "Информация о местоположении",
"link": "Ссылка", "link": "Ссылка",
"links": "Ссылки", "links": "Ссылки",
@ -172,15 +162,12 @@
"edit_collection": "Редактировать коллекцию", "edit_collection": "Редактировать коллекцию",
"unarchive": "Разархивировать", "unarchive": "Разархивировать",
"archive": "Архивировать", "archive": "Архивировать",
"no_collections_found": "Не найдено коллекций для добавления этого приключения.",
"not_visited": "Не посещено", "not_visited": "Не посещено",
"archived_collection_message": "Коллекция успешно архивирована!", "archived_collection_message": "Коллекция успешно архивирована!",
"unarchived_collection_message": "Коллекция успешно разархивирована!", "unarchived_collection_message": "Коллекция успешно разархивирована!",
"delete_collection_success": "Коллекция успешно удалена!", "delete_collection_success": "Коллекция успешно удалена!",
"cancel": "Отмена", "cancel": "Отмена",
"delete_collection": "Удалить коллекцию", "delete_collection": "Удалить коллекцию",
"delete_adventure": "Удалить приключение",
"adventure_delete_success": "Приключение успешно удалено!",
"visited": "Посещено", "visited": "Посещено",
"planned": "Запланировано", "planned": "Запланировано",
"duration": "Продолжительность", "duration": "Продолжительность",
@ -198,17 +185,10 @@
"image_fetch_failed": "Не удалось получить изображение", "image_fetch_failed": "Не удалось получить изображение",
"no_location": "Пожалуйста, введите местоположение", "no_location": "Пожалуйста, введите местоположение",
"no_description_found": "Описание не найдено", "no_description_found": "Описание не найдено",
"adventure_created": "Приключение создано",
"adventure_create_error": "Не удалось создать приключение",
"lodging": "Жильё", "lodging": "Жильё",
"create_adventure": "Создать приключение",
"adventure_updated": "Приключение обновлено",
"adventure_update_error": "Не удалось обновить приключение",
"set_to_pin": "Установить как булавку", "set_to_pin": "Установить как булавку",
"category_fetch_error": "Ошибка получения категорий", "category_fetch_error": "Ошибка получения категорий",
"new_adventure": "Новое приключение",
"basic_information": "Основная информация", "basic_information": "Основная информация",
"no_adventures_to_recommendations": "Приключения не найдены. Добавьте хотя бы одно приключение, чтобы получить рекомендации.",
"display_name": "Отображаемое имя", "display_name": "Отображаемое имя",
"adventure_not_found": "Нет приключений для отображения. Добавьте их, используя кнопку плюс в правом нижнем углу, или попробуйте изменить фильтры!", "adventure_not_found": "Нет приключений для отображения. Добавьте их, используя кнопку плюс в правом нижнем углу, или попробуйте изменить фильтры!",
"no_adventures_found": "Приключения не найдены", "no_adventures_found": "Приключения не найдены",
@ -250,7 +230,6 @@
"no_location_found": "Местоположение не найдено", "no_location_found": "Местоположение не найдено",
"from": "От", "from": "От",
"to": "До", "to": "До",
"will_be_marked": "будет отмечено как посещённое после сохранения приключения.",
"start": "Начало", "start": "Начало",
"end": "Конец", "end": "Конец",
"emoji_picker": "Выбор эмодзи", "emoji_picker": "Выбор эмодзи",
@ -298,7 +277,32 @@
"name_location": "имя, местоположение", "name_location": "имя, местоположение",
"collection_contents": "Содержание коллекции", "collection_contents": "Содержание коллекции",
"check_in": "Регистрироваться", "check_in": "Регистрироваться",
"check_out": "Проверить" "check_out": "Проверить",
"collection_link_location_error": "Ошибка связывания местоположения с сбором",
"collection_link_location_success": "Местоположение, связанное с коллекцией успешно!",
"collection_locations": "Включите места для сбора",
"collection_remove_location_error": "Ошибка удаления местоположения из сбора",
"collection_remove_location_success": "Место удалено из коллекции успешно!",
"create_location": "Создать местоположение",
"delete_location": "Удалить местоположение",
"edit_location": "Редактировать местоположение",
"location_create_error": "Не удалось создать местоположение",
"location_created": "Место создано",
"location_delete_confirm": "Вы уверены, что хотите удалить это место? \nЭто действие не может быть отменено.",
"location_delete_success": "Место удалено успешно!",
"location_not_found": "Местоположение не найдено",
"location_not_found_desc": "Место, которое вы искали, не было найдено. \nПожалуйста, попробуйте другое место или проверьте позже.",
"location_update_error": "Не удалось обновить местоположение",
"location_updated": "Место обновлено",
"new_location": "Новое место",
"no_collections_to_add_location": "Коллекции не обнаружили, чтобы добавить это место.",
"no_locations_to_recommendations": "Никаких мест не найдено. \nДобавьте хотя бы одно место, чтобы получить рекомендации.",
"public_location": "Общественное местоположение",
"share_location": "Поделитесь этим расположением!",
"visit_calendar": "Посетите календарь",
"wiki_location_desc": "Вытягивает отрывок из статьи Википедии, соответствующей названию места.",
"will_be_marked_location": "будет отмечен по посещению после сохранения местоположения.",
"no_locations_found": "Никаких мест не найдено"
}, },
"worldtravel": { "worldtravel": {
"country_list": "Список стран", "country_list": "Список стран",
@ -373,10 +377,10 @@
"public_tooltip": "С публичным профилем пользователи могут делиться с вами коллекциями и просматривать ваш профиль на странице пользователей.", "public_tooltip": "С публичным профилем пользователи могут делиться с вами коллекциями и просматривать ваш профиль на странице пользователей.",
"new_password": "Новый пароль (6+ символов)", "new_password": "Новый пароль (6+ символов)",
"or_3rd_party": "Или войти через сторонний сервис", "or_3rd_party": "Или войти через сторонний сервис",
"no_public_adventures": "Публичные приключения не найдены",
"no_public_collections": "Публичные коллекции не найдены", "no_public_collections": "Публичные коллекции не найдены",
"user_adventures": "Приключения пользователя", "user_collections": "Коллекции пользователя",
"user_collections": "Коллекции пользователя" "no_public_locations": "Общественных мест не найдено",
"user_locations": "Пользовательские местоположения"
}, },
"users": { "users": {
"no_users_found": "Пользователи с публичными профилями не найдены." "no_users_found": "Пользователи с публичными профилями не найдены."
@ -585,7 +589,13 @@
"search": { "search": {
"adventurelog_results": "Результаты AdventureLog", "adventurelog_results": "Результаты AdventureLog",
"public_adventures": "Публичные приключения", "public_adventures": "Публичные приключения",
"online_results": "Онлайн результаты" "online_results": "Онлайн результаты",
"cities": "Города",
"countries": "Страны",
"found": "найденный",
"result": "Результат",
"results": "Результаты",
"try_searching_desc": "Попробуйте искать приключения, коллекции, страны, регионы, города или пользователей."
}, },
"map": { "map": {
"view_details": "Подробности", "view_details": "Подробности",
@ -596,13 +606,16 @@
"clear_marker": "Очистить маркер", "clear_marker": "Очистить маркер",
"add_adventure": "Добавить новое приключение", "add_adventure": "Добавить новое приключение",
"adventure_stats": "Приключенческая статистика", "adventure_stats": "Приключенческая статистика",
"adventures_shown": "Приключения показаны",
"completion": "Завершение", "completion": "Завершение",
"display_options": "Параметры отображения", "display_options": "Параметры отображения",
"map_controls": "Карта управления", "map_controls": "Карта управления",
"marker_placed_on_map": "Маркер размещен на карте", "marker_placed_on_map": "Маркер размещен на карте",
"place_marker_desc": "Нажмите на карту, чтобы разместить маркер, или добавить приключение без местоположения.", "regions": "Регионы",
"regions": "Регионы" "add_location": "Добавить новое место",
"add_location_at_marker": "Добавить новое место в маркере",
"location_map": "Карта местоположения",
"locations_shown": "Места показаны",
"place_marker_desc_location": "Нажмите на карту, чтобы разместить маркер."
}, },
"share": { "share": {
"shared": "Поделено", "shared": "Поделено",
@ -628,20 +641,20 @@
"no_shared_adventures": "Этот пользователь еще не поделился публичными приключениями.", "no_shared_adventures": "Этот пользователь еще не поделился публичными приключениями.",
"no_shared_collections": "Этот пользователь еще не поделился публичными коллекциями.", "no_shared_collections": "Этот пользователь еще не поделился публичными коллекциями.",
"planned_trips": "Запланированные поездки", "planned_trips": "Запланированные поездки",
"public_adventure_experiences": "Общественные приключения",
"travel_statistics": "Статистика путешествий", "travel_statistics": "Статистика путешествий",
"your_journey_at_a_glance": "Ваше приключенческое путешествие с первого взгляда" "your_journey_at_a_glance": "Ваше приключенческое путешествие с первого взгляда",
"public_location_experiences": "Общественное местоположение"
}, },
"categories": { "categories": {
"manage_categories": "Управление категориями", "manage_categories": "Управление категориями",
"no_categories_found": "Категории не найдены.", "no_categories_found": "Категории не найдены.",
"edit_category": "Редактировать категорию", "edit_category": "Редактировать категорию",
"icon": "Иконка", "icon": "Иконка",
"update_after_refresh": "Карточки приключений будут обновлены после обновления страницы.",
"select_category": "Выбрать категорию", "select_category": "Выбрать категорию",
"category_name": "Название категории", "category_name": "Название категории",
"add_new_category": "Добавить новую категорию", "add_new_category": "Добавить новую категорию",
"name_required": "Требуется название категории" "name_required": "Требуется название категории",
"location_update_after_refresh": "Карты местоположения будут обновлены после обновления страницы."
}, },
"dashboard": { "dashboard": {
"welcome_back": "Добро пожаловать обратно", "welcome_back": "Добро пожаловать обратно",
@ -690,22 +703,24 @@
"recomendations": { "recomendations": {
"recommendation": "Рекомендация", "recommendation": "Рекомендация",
"recommendations": "Рекомендации", "recommendations": "Рекомендации",
"adventure_recommendations": "Рекомендации приключений",
"food": "Еда", "food": "Еда",
"tourism": "Туризм" "tourism": "Туризм",
"location_recommendations": "Рекомендации местоположения"
}, },
"calendar": { "calendar": {
"all_categories": "Все категории",
"all_day_event": "Событие на весь день", "all_day_event": "Событие на весь день",
"calendar_overview": "Обзор календаря", "calendar_overview": "Обзор календаря",
"categories": "Категории",
"day": "День", "day": "День",
"events_scheduled": "События запланированы", "events_scheduled": "События запланированы",
"filter_by_category": "Фильтр по категории",
"filtered_results": "Отфильтрованные результаты", "filtered_results": "Отфильтрованные результаты",
"month": "Месяц", "month": "Месяц",
"today": "Сегодня", "today": "Сегодня",
"total_events": "Общее количество событий", "total_events": "Общее количество событий",
"week": "Неделя" "week": "Неделя"
},
"locations": {
"location": "Расположение",
"locations": "Локации",
"my_locations": "Мои локации"
} }
} }

View file

@ -15,8 +15,6 @@
"activities": {}, "activities": {},
"add_to_collection": "Lägg till i samlingen", "add_to_collection": "Lägg till i samlingen",
"adventure": "Äventyr", "adventure": "Äventyr",
"adventure_delete_confirm": "Är du säker på att du vill ta bort det här äventyret? \nDenna åtgärd kan inte ångras.",
"adventure_delete_success": "Äventyret har raderats!",
"archive": "Arkiv", "archive": "Arkiv",
"archived": "Arkiverad", "archived": "Arkiverad",
"archived_collection_message": "Samlingen har arkiverats!", "archived_collection_message": "Samlingen har arkiverats!",
@ -27,16 +25,11 @@
"clear": "Rensa", "clear": "Rensa",
"collection": "Samling", "collection": "Samling",
"collection_adventures": "Inkludera samlingsäventyr", "collection_adventures": "Inkludera samlingsäventyr",
"collection_link_error": "Det gick inte att länka äventyr till samling",
"collection_link_success": "Äventyr kopplat till samling framgångsrikt!",
"collection_remove_error": "Det gick inte att ta bort äventyr från samlingen",
"collection_remove_success": "Äventyret har tagits bort från samlingen!",
"count_txt": "resultat som matchar din sökning", "count_txt": "resultat som matchar din sökning",
"create_new": "Skapa nytt...", "create_new": "Skapa nytt...",
"date": "Datum", "date": "Datum",
"dates": "Datum", "dates": "Datum",
"delete": "Radera", "delete": "Radera",
"delete_adventure": "Ta bort äventyr",
"delete_collection": "Ta bort samling", "delete_collection": "Ta bort samling",
"delete_collection_success": "Samlingen har raderats!", "delete_collection_success": "Samlingen har raderats!",
"descending": "Fallande", "descending": "Fallande",
@ -50,8 +43,6 @@
"my_collections": "Mina samlingar", "my_collections": "Mina samlingar",
"name": "Namn", "name": "Namn",
"no_image_found": "Ingen bild hittades", "no_image_found": "Ingen bild hittades",
"not_found": "Äventyret hittades inte",
"not_found_desc": "Äventyret du letade efter kunde inte hittas. \nProva ett annat äventyr eller kom tillbaka senare.",
"open_details": "Öppna Detaljer", "open_details": "Öppna Detaljer",
"open_filters": "Öppna filter", "open_filters": "Öppna filter",
"order_by": "Sortera efter", "order_by": "Sortera efter",
@ -81,10 +72,6 @@
"activity_types": "Aktivitetstyper", "activity_types": "Aktivitetstyper",
"add": "Tillägga", "add": "Tillägga",
"add_notes": "Lägg till anteckningar", "add_notes": "Lägg till anteckningar",
"adventure_create_error": "Det gick inte att skapa äventyr",
"adventure_created": "Äventyr skapat",
"adventure_update_error": "Det gick inte att uppdatera äventyret",
"adventure_updated": "Äventyr uppdaterat",
"basic_information": "Grundläggande information", "basic_information": "Grundläggande information",
"category": "Kategori", "category": "Kategori",
"clear_map": "Rensa karta", "clear_map": "Rensa karta",
@ -100,30 +87,25 @@
"location": "Plats", "location": "Plats",
"location_information": "Platsinformation", "location_information": "Platsinformation",
"my_images": "Mina bilder", "my_images": "Mina bilder",
"new_adventure": "Nytt äventyr",
"no_description_found": "Ingen beskrivning hittades", "no_description_found": "Ingen beskrivning hittades",
"no_images": "Inga bilder", "no_images": "Inga bilder",
"no_location": "Vänligen ange en plats", "no_location": "Vänligen ange en plats",
"no_results": "Inga resultat hittades", "no_results": "Inga resultat hittades",
"public_adventure": "Offentligt äventyr",
"remove": "Ta bort", "remove": "Ta bort",
"save_next": "Spara", "save_next": "Spara",
"search_for_location": "Sök efter en plats", "search_for_location": "Sök efter en plats",
"search_results": "Sökresultat", "search_results": "Sökresultat",
"see_adventures": "Se äventyr", "see_adventures": "Se äventyr",
"share_adventure": "Dela detta äventyr!",
"start_date": "Startdatum", "start_date": "Startdatum",
"upload_image": "Ladda upp bild", "upload_image": "Ladda upp bild",
"url": "URL", "url": "URL",
"warning": "Varning", "warning": "Varning",
"wiki_desc": "Hämtar utdrag från Wikipedia-artikeln som matchar äventyrets namn.",
"adventure_not_found": "Det finns inga äventyr att visa upp. \nLägg till några med hjälp av plusknappen längst ner till höger eller prova att byta filter!", "adventure_not_found": "Det finns inga äventyr att visa upp. \nLägg till några med hjälp av plusknappen längst ner till höger eller prova att byta filter!",
"all": "Alla", "all": "Alla",
"error_updating_regions": "Fel vid uppdatering av regioner", "error_updating_regions": "Fel vid uppdatering av regioner",
"mark_visited": "Markera som besökt", "mark_visited": "Markera som besökt",
"my_adventures": "Mina äventyr", "my_adventures": "Mina äventyr",
"no_adventures_found": "Inga äventyr hittades", "no_adventures_found": "Inga äventyr hittades",
"no_collections_found": "Inga samlingar hittades att lägga till detta äventyr till.",
"no_linkable_adventures": "Inga äventyr hittades som kan kopplas till denna samling.", "no_linkable_adventures": "Inga äventyr hittades som kan kopplas till denna samling.",
"not_visited": "Ej besökta", "not_visited": "Ej besökta",
"regions_updated": "regioner uppdaterade", "regions_updated": "regioner uppdaterade",
@ -181,10 +163,7 @@
"starting_airport": "Startar flygplats", "starting_airport": "Startar flygplats",
"to": "Till", "to": "Till",
"transportation_delete_confirm": "Är du säker på att du vill ta bort denna transport? \nDenna åtgärd kan inte ångras.", "transportation_delete_confirm": "Är du säker på att du vill ta bort denna transport? \nDenna åtgärd kan inte ångras.",
"will_be_marked": "kommer att markeras som besökt när äventyret har sparats.",
"cities_updated": "städer uppdaterade", "cities_updated": "städer uppdaterade",
"create_adventure": "Skapa äventyr",
"no_adventures_to_recommendations": "Inga äventyr hittades. \nLägg till minst ett äventyr för att få rekommendationer.",
"finding_recommendations": "Upptäck dolda pärlor för ditt nästa äventyr", "finding_recommendations": "Upptäck dolda pärlor för ditt nästa äventyr",
"attachment": "Fastsättning", "attachment": "Fastsättning",
"attachment_delete_success": "Bilagan har raderats!", "attachment_delete_success": "Bilagan har raderats!",
@ -246,7 +225,32 @@
"name_location": "namn, plats", "name_location": "namn, plats",
"collection_contents": "Insamlingsinnehåll", "collection_contents": "Insamlingsinnehåll",
"check_in": "Checka in", "check_in": "Checka in",
"check_out": "Checka ut" "check_out": "Checka ut",
"collection_link_location_error": "Fel som länkar plats till insamling",
"collection_link_location_success": "Plats kopplad till samling framgångsrikt!",
"collection_locations": "Inkludera insamlingsplatser",
"collection_remove_location_error": "Fel att ta bort platsen från samlingen",
"collection_remove_location_success": "Plats tas bort från samlingen framgångsrikt!",
"create_location": "Skapa plats",
"delete_location": "Radera plats",
"edit_location": "Redigera plats",
"location_create_error": "Det gick inte att skapa plats",
"location_created": "Plats skapad",
"location_delete_confirm": "Är du säker på att du vill ta bort den här platsen? \nDenna åtgärd kan inte ångras.",
"location_delete_success": "Plats raderas framgångsrikt!",
"location_not_found": "Plats hittades inte",
"location_not_found_desc": "Platsen du letade efter kunde inte hittas. \nFörsök med en annan plats eller kom tillbaka senare.",
"location_update_error": "Det gick inte att uppdatera platsen",
"location_updated": "Plats uppdaterad",
"new_location": "Ny plats",
"no_collections_to_add_location": "Inga samlingar som hittats för att lägga till den här platsen till.",
"no_locations_to_recommendations": "Inga platser hittades. \nLägg till minst en plats för att få rekommendationer.",
"public_location": "Allmän plats",
"share_location": "Dela den här platsen!",
"visit_calendar": "Besök kalendern",
"wiki_location_desc": "Drar utdrag från Wikipedia -artikeln som matchar namnet på platsen.",
"will_be_marked_location": "kommer att markeras som besöks när platsen har sparats.",
"no_locations_found": "Inga platser hittades"
}, },
"home": { "home": {
"desc_1": "Upptäck, planera och utforska med lätthet", "desc_1": "Upptäck, planera och utforska med lätthet",
@ -373,10 +377,10 @@
"public_profile": "Offentlig profil", "public_profile": "Offentlig profil",
"new_password": "Nytt lösenord", "new_password": "Nytt lösenord",
"or_3rd_party": "Eller logga in med en tredjepartstjänst", "or_3rd_party": "Eller logga in med en tredjepartstjänst",
"no_public_adventures": "Inga offentliga äventyr hittades",
"no_public_collections": "Inga offentliga samlingar hittades", "no_public_collections": "Inga offentliga samlingar hittades",
"user_adventures": "Användaräventyr", "user_collections": "Användarsamlingar",
"user_collections": "Användarsamlingar" "no_public_locations": "Inga offentliga platser hittades",
"user_locations": "Användarplatser"
}, },
"users": { "users": {
"no_users_found": "Inga användare hittades med offentliga profiler." "no_users_found": "Inga användare hittades med offentliga profiler."
@ -568,7 +572,13 @@
"search": { "search": {
"adventurelog_results": "AdventureLog-resultat", "adventurelog_results": "AdventureLog-resultat",
"online_results": "Online resultat", "online_results": "Online resultat",
"public_adventures": "Offentliga äventyr" "public_adventures": "Offentliga äventyr",
"cities": "Städer",
"countries": "Länder",
"found": "funnna",
"result": "Resultat",
"results": "Resultat",
"try_searching_desc": "Försök att söka efter äventyr, samlingar, länder, regioner, städer eller användare."
}, },
"map": { "map": {
"add_adventure": "Lägg till nytt äventyr", "add_adventure": "Lägg till nytt äventyr",
@ -579,13 +589,16 @@
"show_visited_regions": "Visa besökta regioner", "show_visited_regions": "Visa besökta regioner",
"view_details": "Visa detaljer", "view_details": "Visa detaljer",
"adventure_stats": "Äventyrsstatistik", "adventure_stats": "Äventyrsstatistik",
"adventures_shown": "Äventyr visas",
"completion": "Komplettering", "completion": "Komplettering",
"display_options": "Visningsalternativ", "display_options": "Visningsalternativ",
"map_controls": "Kartkontroller", "map_controls": "Kartkontroller",
"marker_placed_on_map": "Markör placerad på kartan", "marker_placed_on_map": "Markör placerad på kartan",
"place_marker_desc": "Klicka på kartan för att placera en markör, eller lägg till ett äventyr utan plats.", "regions": "Regioner",
"regions": "Regioner" "add_location": "Lägg till en ny plats",
"add_location_at_marker": "Lägg till en ny plats på Marker",
"location_map": "Platskarta",
"locations_shown": "Visas",
"place_marker_desc_location": "Klicka på kartan för att placera en markör."
}, },
"languages": {}, "languages": {},
"share": { "share": {
@ -611,9 +624,9 @@
"no_shared_adventures": "Den här användaren har inte delat några offentliga äventyr än.", "no_shared_adventures": "Den här användaren har inte delat några offentliga äventyr än.",
"no_shared_collections": "Den här användaren har inte delat några offentliga samlingar än.", "no_shared_collections": "Den här användaren har inte delat några offentliga samlingar än.",
"planned_trips": "Planerade resor", "planned_trips": "Planerade resor",
"public_adventure_experiences": "Allmänt äventyrsupplevelser",
"travel_statistics": "Resestatistik", "travel_statistics": "Resestatistik",
"your_journey_at_a_glance": "Din äventyrsresa med en överblick" "your_journey_at_a_glance": "Din äventyrsresa med en överblick",
"public_location_experiences": "Allmän platsupplevelser"
}, },
"categories": { "categories": {
"category_name": "Kategorinamn", "category_name": "Kategorinamn",
@ -622,9 +635,9 @@
"manage_categories": "Hantera kategorier", "manage_categories": "Hantera kategorier",
"no_categories_found": "Inga kategorier hittades.", "no_categories_found": "Inga kategorier hittades.",
"select_category": "Välj Kategori", "select_category": "Välj Kategori",
"update_after_refresh": "Äventyrskorten kommer att uppdateras när du uppdaterar sidan.",
"add_new_category": "Lägg till en ny kategori", "add_new_category": "Lägg till en ny kategori",
"name_required": "Kategorinamn krävs" "name_required": "Kategorinamn krävs",
"location_update_after_refresh": "Platskorten kommer att uppdateras när du uppdaterar sidan."
}, },
"dashboard": { "dashboard": {
"add_some": "Varför inte börja planera ditt nästa äventyr? \nDu kan lägga till ett nytt äventyr genom att klicka på knappen nedan.", "add_some": "Varför inte börja planera ditt nästa äventyr? \nDu kan lägga till ett nytt äventyr genom att klicka på knappen nedan.",
@ -670,9 +683,9 @@
"recomendations": { "recomendations": {
"recommendation": "Rekommendation", "recommendation": "Rekommendation",
"recommendations": "Rekommendationer", "recommendations": "Rekommendationer",
"adventure_recommendations": "Äventyrsrekommendationer",
"food": "Mat", "food": "Mat",
"tourism": "Turism" "tourism": "Turism",
"location_recommendations": "Platsrekommendationer"
}, },
"lodging": { "lodging": {
"apartment": "Lägenhet", "apartment": "Lägenhet",
@ -695,17 +708,19 @@
"google_maps_integration_desc": "Anslut ditt Google Maps-konto för att få sökresultat och rekommendationer av hög kvalitet." "google_maps_integration_desc": "Anslut ditt Google Maps-konto för att få sökresultat och rekommendationer av hög kvalitet."
}, },
"calendar": { "calendar": {
"all_categories": "Alla kategorier",
"all_day_event": "Hela dagen", "all_day_event": "Hela dagen",
"calendar_overview": "Kalenderöversikt", "calendar_overview": "Kalenderöversikt",
"categories": "Kategorier",
"day": "Dag", "day": "Dag",
"events_scheduled": "Händelser planerade", "events_scheduled": "Händelser planerade",
"filter_by_category": "Filter efter kategori",
"filtered_results": "Filtrerade resultat", "filtered_results": "Filtrerade resultat",
"month": "Månad", "month": "Månad",
"today": "I dag", "today": "I dag",
"total_events": "Totala evenemang", "total_events": "Totala evenemang",
"week": "Vecka" "week": "Vecka"
},
"locations": {
"location": "Plats",
"locations": "Plats",
"my_locations": "Mina platser"
} }
} }

View file

@ -64,12 +64,7 @@
"start_your_journey": "开始您的旅程" "start_your_journey": "开始您的旅程"
}, },
"adventures": { "adventures": {
"collection_remove_success": "成功从合集中移除冒险!",
"collection_remove_error": "从合集中移除冒险时出错",
"collection_link_success": "成功将冒险链接到合集!",
"no_image_found": "未找到图片", "no_image_found": "未找到图片",
"collection_link_error": "链接冒险到合集时出错",
"adventure_delete_confirm": "您确定要删除此冒险吗?此操作无法撤销。",
"checklist_delete_confirm": "您确定要删除此检查清单吗?此操作无法撤销。", "checklist_delete_confirm": "您确定要删除此检查清单吗?此操作无法撤销。",
"note_delete_confirm": "您确定要删除此笔记吗?此操作无法撤销。", "note_delete_confirm": "您确定要删除此笔记吗?此操作无法撤销。",
"transportation_delete_confirm": "您确定要删除此交通工具吗?此操作无法撤销。", "transportation_delete_confirm": "您确定要删除此交通工具吗?此操作无法撤销。",
@ -83,8 +78,6 @@
"remove_from_collection": "从合集中移除", "remove_from_collection": "从合集中移除",
"add_to_collection": "添加到合集", "add_to_collection": "添加到合集",
"delete": "删除", "delete": "删除",
"not_found": "未找到冒险",
"not_found_desc": "未找到你要查找的冒险。请尝试其他冒险或稍后再试。",
"homepage": "主页", "homepage": "主页",
"collection": "合集", "collection": "合集",
"longitude": "经度", "longitude": "经度",
@ -109,7 +102,6 @@
"rating": "评分", "rating": "评分",
"my_images": "我的图片", "my_images": "我的图片",
"no_images": "没有图片", "no_images": "没有图片",
"share_adventure": "分享此冒险!",
"copy_link": "复制链接", "copy_link": "复制链接",
"image": "图片", "image": "图片",
"upload_image": "上传图片", "upload_image": "上传图片",
@ -130,12 +122,10 @@
"clear_map": "清除地图", "clear_map": "清除地图",
"search_results": "搜索结果", "search_results": "搜索结果",
"no_results": "未找到结果", "no_results": "未找到结果",
"wiki_desc": "从与冒险名称匹配的维基百科文章中提取摘录。",
"attachments": "附件", "attachments": "附件",
"attachment": "附件", "attachment": "附件",
"images": "图片", "images": "图片",
"generate_desc": "生成描述", "generate_desc": "生成描述",
"public_adventure": "公开冒险",
"location_information": "位置信息", "location_information": "位置信息",
"link": "链接", "link": "链接",
"links": "链接", "links": "链接",
@ -156,15 +146,12 @@
"edit_collection": "编辑合集", "edit_collection": "编辑合集",
"unarchive": "取消归档", "unarchive": "取消归档",
"archive": "归档", "archive": "归档",
"no_collections_found": "未找到可添加此冒险的合集。",
"not_visited": "未访问", "not_visited": "未访问",
"archived_collection_message": "成功归档合集!", "archived_collection_message": "成功归档合集!",
"unarchived_collection_message": "成功取消归档合集!", "unarchived_collection_message": "成功取消归档合集!",
"delete_collection_success": "成功删除合集!", "delete_collection_success": "成功删除合集!",
"cancel": "取消", "cancel": "取消",
"delete_collection": "删除合集", "delete_collection": "删除合集",
"delete_adventure": "删除冒险",
"adventure_delete_success": "成功删除冒险!",
"visited": "已访问", "visited": "已访问",
"planned": "计划中", "planned": "计划中",
"duration": "持续时间", "duration": "持续时间",
@ -182,17 +169,10 @@
"image_fetch_failed": "获取图片失败", "image_fetch_failed": "获取图片失败",
"no_location": "请输入位置", "no_location": "请输入位置",
"no_description_found": "未找到描述", "no_description_found": "未找到描述",
"adventure_created": "冒险已创建",
"adventure_create_error": "创建冒险失败",
"lodging": "住宿", "lodging": "住宿",
"create_adventure": "创建冒险",
"adventure_updated": "冒险已更新",
"adventure_update_error": "更新冒险失败",
"set_to_pin": "设置为图钉", "set_to_pin": "设置为图钉",
"category_fetch_error": "获取类别时出错", "category_fetch_error": "获取类别时出错",
"new_adventure": "新冒险",
"basic_information": "基本信息", "basic_information": "基本信息",
"no_adventures_to_recommendations": "未找到冒险。添加至少一个冒险以获得推荐。",
"display_name": "显示名称", "display_name": "显示名称",
"adventure_not_found": "没找到任何冒险。使用右下角的加号按钮添加一些,或尝试更改筛选条件!", "adventure_not_found": "没找到任何冒险。使用右下角的加号按钮添加一些,或尝试更改筛选条件!",
"no_adventures_found": "未找到冒险", "no_adventures_found": "未找到冒险",
@ -232,7 +212,6 @@
"no_location_found": "未找到位置", "no_location_found": "未找到位置",
"from": "从", "from": "从",
"to": "到", "to": "到",
"will_be_marked": "将在冒险保存后标记为已访问。",
"start": "开始", "start": "开始",
"end": "结束", "end": "结束",
"emoji_picker": "表情符号选择器", "emoji_picker": "表情符号选择器",
@ -298,7 +277,32 @@
"name_location": "名称,位置", "name_location": "名称,位置",
"collection_contents": "收集内容", "collection_contents": "收集内容",
"check_in": "报到", "check_in": "报到",
"check_out": "查看" "check_out": "查看",
"collection_link_location_error": "链接位置到集合的错误",
"collection_link_location_success": "成功链接到收集的位置!",
"collection_locations": "包括收集位置",
"collection_remove_location_error": "从集合中删除位置的错误",
"collection_remove_location_success": "成功从收藏中删除的位置!",
"create_location": "创建位置",
"delete_location": "删除位置",
"edit_location": "编辑位置",
"location_create_error": "无法创建位置",
"location_created": "创建的位置",
"location_delete_confirm": "您确定要删除此位置吗?\n该动作不能撤消。",
"location_delete_success": "位置成功删除了!",
"location_not_found": "找不到位置",
"location_not_found_desc": "找不到您寻找的位置。\n请尝试其他位置或稍后再检查。",
"location_update_error": "无法更新位置",
"location_updated": "位置更新",
"new_location": "新位置",
"no_collections_to_add_location": "没有发现将此位置添加到。",
"no_locations_to_recommendations": "找不到位置。\n添加至少一个位置以获取建议。",
"public_location": "公共位置",
"share_location": "分享这个位置!",
"visit_calendar": "访问日历",
"wiki_location_desc": "从Wikipedia文章中提取摘录符合该位置的名称。",
"will_be_marked_location": "保存位置后,将被标记为访问。",
"no_locations_found": "找不到位置"
}, },
"auth": { "auth": {
"forgot_password": "忘记密码?", "forgot_password": "忘记密码?",
@ -317,10 +321,10 @@
"public_tooltip": "通过公开个人资料,用户可以与您共享合集,并在用户页面查看您的资料。", "public_tooltip": "通过公开个人资料,用户可以与您共享合集,并在用户页面查看您的资料。",
"new_password": "新密码6个字符以上", "new_password": "新密码6个字符以上",
"or_3rd_party": "或使用第三方服务登录", "or_3rd_party": "或使用第三方服务登录",
"no_public_adventures": "未找到公开冒险",
"no_public_collections": "未找到公开合集", "no_public_collections": "未找到公开合集",
"user_adventures": "用户冒险", "user_collections": "用户合集",
"user_collections": "用户合集" "no_public_locations": "找不到公共场所",
"user_locations": "用户位置"
}, },
"worldtravel": { "worldtravel": {
"all": "全部", "all": "全部",
@ -568,7 +572,13 @@
"search": { "search": {
"adventurelog_results": "AdventureLog 结果", "adventurelog_results": "AdventureLog 结果",
"online_results": "在线结果", "online_results": "在线结果",
"public_adventures": "已公开的冒险" "public_adventures": "已公开的冒险",
"cities": "城市",
"countries": "国家",
"found": "成立",
"result": "结果",
"results": "结果",
"try_searching_desc": "尝试搜索冒险,收藏,国家,地区,城市或用户。"
}, },
"map": { "map": {
"add_adventure": "添加新冒险", "add_adventure": "添加新冒险",
@ -579,13 +589,16 @@
"show_visited_regions": "显示访问过的地区", "show_visited_regions": "显示访问过的地区",
"view_details": "查看详情", "view_details": "查看详情",
"adventure_stats": "冒险统计", "adventure_stats": "冒险统计",
"adventures_shown": "展示的冒险",
"completion": "完成", "completion": "完成",
"display_options": "显示选项", "display_options": "显示选项",
"map_controls": "地图控件", "map_controls": "地图控件",
"marker_placed_on_map": "放置在地图上的标记", "marker_placed_on_map": "放置在地图上的标记",
"place_marker_desc": "单击地图以放置标记,或在没有位置的情况下添加冒险。", "regions": "地区",
"regions": "地区" "add_location": "添加新位置",
"add_location_at_marker": "在标记处添加新位置",
"location_map": "位置图",
"locations_shown": "显示的位置",
"place_marker_desc_location": "单击地图以放置标记。"
}, },
"languages": {}, "languages": {},
"share": { "share": {
@ -611,9 +624,9 @@
"no_shared_adventures": "该用户尚未分享任何公共冒险。", "no_shared_adventures": "该用户尚未分享任何公共冒险。",
"no_shared_collections": "该用户尚未共享任何公共收藏。", "no_shared_collections": "该用户尚未共享任何公共收藏。",
"planned_trips": "计划的旅行", "planned_trips": "计划的旅行",
"public_adventure_experiences": "公共冒险经验",
"travel_statistics": "旅行统计", "travel_statistics": "旅行统计",
"your_journey_at_a_glance": "您一眼的冒险之旅" "your_journey_at_a_glance": "您一眼的冒险之旅",
"public_location_experiences": "公共位置经验"
}, },
"categories": { "categories": {
"category_name": "类别名称", "category_name": "类别名称",
@ -622,9 +635,9 @@
"manage_categories": "管理类别", "manage_categories": "管理类别",
"no_categories_found": "未找到类别。", "no_categories_found": "未找到类别。",
"select_category": "选择类别", "select_category": "选择类别",
"update_after_refresh": "刷新页面后,冒险卡将更新。",
"add_new_category": "添加新类别", "add_new_category": "添加新类别",
"name_required": "需要类别名称" "name_required": "需要类别名称",
"location_update_after_refresh": "刷新页面后,将更新位置卡。"
}, },
"dashboard": { "dashboard": {
"add_some": "为什么不开始计划你的下一次冒险呢?\n您可以通过单击下面的按钮添加新的冒险。", "add_some": "为什么不开始计划你的下一次冒险呢?\n您可以通过单击下面的按钮添加新的冒险。",
@ -670,9 +683,9 @@
"recomendations": { "recomendations": {
"recommendation": "推荐", "recommendation": "推荐",
"recommendations": "建议", "recommendations": "建议",
"adventure_recommendations": "冒险建议",
"food": "食物", "food": "食物",
"tourism": "旅游" "tourism": "旅游",
"location_recommendations": "位置建议"
}, },
"lodging": { "lodging": {
"campground": "露营地", "campground": "露营地",
@ -695,17 +708,19 @@
"google_maps_integration_desc": "连接您的Google Maps帐户以获取高质量的位置搜索结果和建议。" "google_maps_integration_desc": "连接您的Google Maps帐户以获取高质量的位置搜索结果和建议。"
}, },
"calendar": { "calendar": {
"all_categories": "所有类别",
"all_day_event": "全天活动", "all_day_event": "全天活动",
"calendar_overview": "日历概述", "calendar_overview": "日历概述",
"categories": "类别",
"day": "天", "day": "天",
"events_scheduled": "预定事件", "events_scheduled": "预定事件",
"filter_by_category": "按类别过滤",
"filtered_results": "过滤结果", "filtered_results": "过滤结果",
"month": "月", "month": "月",
"today": "今天", "today": "今天",
"total_events": "总事件", "total_events": "总事件",
"week": "星期" "week": "星期"
},
"locations": {
"location": "地点",
"locations": "位置",
"my_locations": "我的位置"
} }
} }

View file

@ -102,7 +102,7 @@
<div class="flex flex-col sm:flex-row gap-4 pt-4"> <div class="flex flex-col sm:flex-row gap-4 pt-4">
{#if data.user} {#if data.user}
<button <button
on:click={() => goto('/adventures')} on:click={() => goto('/locations')}
class="btn btn-primary btn-lg gap-3 shadow-lg hover:shadow-xl transition-all duration-300 group" class="btn btn-primary btn-lg gap-3 shadow-lg hover:shadow-xl transition-all duration-300 group"
> >
<PlayIcon class="w-5 h-5 group-hover:scale-110 transition-transform" /> <PlayIcon class="w-5 h-5 group-hover:scale-110 transition-transform" />

View file

@ -8,7 +8,7 @@ export const POST: RequestHandler = async (event) => {
let allActivities: string[] = []; let allActivities: string[] = [];
let csrfToken = await fetchCSRFToken(); let csrfToken = await fetchCSRFToken();
let sessionId = event.cookies.get('sessionid'); let sessionId = event.cookies.get('sessionid');
let res = await event.fetch(`${endpoint}/api/activity-types/types/`, { let res = await event.fetch(`${endpoint}/api/tags/types/`, {
headers: { headers: {
'X-CSRFToken': csrfToken, 'X-CSRFToken': csrfToken,
Cookie: `csrftoken=${csrfToken}; sessionid=${sessionId}` Cookie: `csrftoken=${csrfToken}; sessionid=${sessionId}`

View file

@ -1,99 +1,5 @@
import { redirect } from '@sveltejs/kit'; import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
const PUBLIC_SERVER_URL = process.env['PUBLIC_SERVER_URL'];
import type { Adventure } from '$lib/types';
import type { Actions } from '@sveltejs/kit'; export const load = (async (_event) => {
import { fetchCSRFToken } from '$lib/index.server'; return redirect(301, '/locations');
}) satisfies import('./$types').PageServerLoad;
const serverEndpoint = PUBLIC_SERVER_URL || 'http://localhost:8000';
export const load = (async (event) => {
if (!event.locals.user) {
return redirect(302, '/login');
} else {
let count = 0;
let adventures: Adventure[] = [];
let typeString = event.url.searchParams.get('types');
// If no type is specified, default to 'all'
if (!typeString) {
typeString = 'all';
}
const include_collections = event.url.searchParams.get('include_collections') || 'false';
const order_by = event.url.searchParams.get('order_by') || 'updated_at';
const order_direction = event.url.searchParams.get('order_direction') || 'asc';
const page = event.url.searchParams.get('page') || '1';
const is_visited = event.url.searchParams.get('is_visited') || 'all';
let initialFetch = await event.fetch(
`${serverEndpoint}/api/adventures/filtered?types=${typeString}&order_by=${order_by}&order_direction=${order_direction}&include_collections=${include_collections}&page=${page}&is_visited=${is_visited}`,
{
headers: {
Cookie: `sessionid=${event.cookies.get('sessionid')}`
},
credentials: 'include'
}
);
if (!initialFetch.ok) {
let error_message = await initialFetch.json();
console.error(error_message);
console.error('Failed to fetch visited adventures');
return redirect(302, '/login');
} else {
let res = await initialFetch.json();
let visited = res.results as Adventure[];
count = res.count;
adventures = [...adventures, ...visited];
}
return {
props: {
adventures,
count
}
};
}
}) satisfies PageServerLoad;
export const actions: Actions = {
image: async (event) => {
let formData = await event.request.formData();
let csrfToken = await fetchCSRFToken();
let sessionId = event.cookies.get('sessionid');
let res = await fetch(`${serverEndpoint}/api/images/`, {
method: 'POST',
headers: {
Cookie: `csrftoken=${csrfToken}; sessionid=${sessionId}`,
'X-CSRFToken': csrfToken,
Referer: event.url.origin // Include Referer header
},
body: formData
});
let data = await res.json();
return data;
},
attachment: async (event) => {
let formData = await event.request.formData();
let csrfToken = await fetchCSRFToken();
let sessionId = event.cookies.get('sessionid');
let res = await fetch(`${serverEndpoint}/api/attachments/`, {
method: 'POST',
headers: {
Cookie: `csrftoken=${csrfToken}; sessionid=${sessionId}`,
'X-CSRFToken': csrfToken,
Referer: event.url.origin // Include Referer header
},
body: formData
});
let data = await res.json();
console.log(res);
console.log(data);
return data;
}
};

View file

@ -1,76 +1,7 @@
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
const PUBLIC_SERVER_URL = process.env['PUBLIC_SERVER_URL']; import { redirect } from '@sveltejs/kit';
import type { AdditionalAdventure, Adventure, Collection } from '$lib/types';
const endpoint = PUBLIC_SERVER_URL || 'http://localhost:8000';
export const load = (async (event) => { export const load = (async (event) => {
const id = event.params as { id: string }; const id = event.params as { id: string };
let request = await fetch(`${endpoint}/api/adventures/${id.id}/additional-info/`, { return redirect(301, `/locations/${id.id}`);
headers: {
Cookie: `sessionid=${event.cookies.get('sessionid')}`
},
credentials: 'include'
});
if (!request.ok) {
console.error('Failed to fetch adventure ' + id.id);
return {
props: {
adventure: null
}
};
} else {
let adventure = (await request.json()) as AdditionalAdventure;
return {
props: {
adventure
}
};
}
}) satisfies PageServerLoad; }) satisfies PageServerLoad;
import { redirect, type Actions } from '@sveltejs/kit';
import { fetchCSRFToken } from '$lib/index.server';
const serverEndpoint = PUBLIC_SERVER_URL || 'http://localhost:8000';
export const actions: Actions = {
delete: async (event) => {
const id = event.params as { id: string };
const adventureId = id.id;
if (!event.locals.user) {
return redirect(302, '/login');
}
if (!adventureId) {
return {
status: 400,
error: new Error('Bad request')
};
}
let csrfToken = await fetchCSRFToken();
let res = await fetch(`${serverEndpoint}/api/adventures/${event.params.id}`, {
method: 'DELETE',
headers: {
Referer: event.url.origin, // Include Referer header
Cookie: `sessionid=${event.cookies.get('sessionid')};
csrftoken=${csrfToken}`,
'X-CSRFToken': csrfToken
},
credentials: 'include'
});
console.log(res);
if (!res.ok) {
return {
status: res.status,
error: new Error('Failed to delete adventure')
};
} else {
return {
status: 204
};
}
}
};

View file

@ -1,4 +1,4 @@
import type { Adventure } from '$lib/types'; import type { Location } from '$lib/types';
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
import { formatDateInTimezone, formatAllDayDate } from '$lib/dateUtils'; import { formatDateInTimezone, formatAllDayDate } from '$lib/dateUtils';
import { isAllDay } from '$lib'; import { isAllDay } from '$lib';
@ -8,12 +8,12 @@ const endpoint = PUBLIC_SERVER_URL || 'http://localhost:8000';
export const load = (async (event) => { export const load = (async (event) => {
let sessionId = event.cookies.get('sessionid'); let sessionId = event.cookies.get('sessionid');
let visitedFetch = await fetch(`${endpoint}/api/adventures/all/?include_collections=true`, { let visitedFetch = await fetch(`${endpoint}/api/locations/all/?include_collections=true`, {
headers: { headers: {
Cookie: `sessionid=${sessionId}` Cookie: `sessionid=${sessionId}`
} }
}); });
let adventures = (await visitedFetch.json()) as Adventure[]; let adventures = (await visitedFetch.json()) as Location[];
// Get user's local timezone as fallback // Get user's local timezone as fallback
const userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone; const userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;

View file

@ -26,7 +26,7 @@
return marked(markdown); return marked(markdown);
}; };
let adventures = data.props.adventures; let locations = data.props.adventures;
let allDates = data.props.dates; let allDates = data.props.dates;
let filteredDates = [...allDates]; let filteredDates = [...allDates];
@ -174,7 +174,7 @@
</script> </script>
<svelte:head> <svelte:head>
<title>{$t('adventures.adventure_calendar')} - AdventureLog</title> <title>{$t('adventures.visit_calendar')} - AdventureLog</title>
</svelte:head> </svelte:head>
<div class="min-h-screen bg-gradient-to-br from-base-200 via-base-100 to-base-200"> <div class="min-h-screen bg-gradient-to-br from-base-200 via-base-100 to-base-200">
@ -196,7 +196,7 @@
</div> </div>
<div> <div>
<h1 class="text-3xl font-bold text-primary bg-clip-text"> <h1 class="text-3xl font-bold text-primary bg-clip-text">
{$t('adventures.adventure_calendar')} {$t('adventures.visit_calendar')}
</h1> </h1>
<p class="text-sm text-base-content/60"> <p class="text-sm text-base-content/60">
{filteredDates.length} {filteredDates.length}
@ -214,8 +214,8 @@
<div class="stat-value text-lg text-primary">{allDates.length}</div> <div class="stat-value text-lg text-primary">{allDates.length}</div>
</div> </div>
<div class="stat py-2 px-4"> <div class="stat py-2 px-4">
<div class="stat-title text-xs">{$t('navbar.adventures')}</div> <div class="stat-title text-xs">{$t('locations.locations')}</div>
<div class="stat-value text-lg text-secondary">{adventures.length}</div> <div class="stat-value text-lg text-secondary">{locations.length}</div>
</div> </div>
</div> </div>
</div> </div>
@ -229,7 +229,7 @@
/> />
<input <input
type="text" type="text"
placeholder="Search adventures or locations..." placeholder={$t('adventures.search_for_location')}
class="input input-bordered w-full pl-10 pr-10 bg-base-100/80" class="input input-bordered w-full pl-10 pr-10 bg-base-100/80"
bind:value={searchFilter} bind:value={searchFilter}
/> />
@ -298,8 +298,8 @@
<div class="grid grid-cols-2 gap-4"> <div class="grid grid-cols-2 gap-4">
<div class="stat p-0"> <div class="stat p-0">
<div class="stat-title text-xs">{$t('navbar.adventures')}</div> <div class="stat-title text-xs">{$t('locations.locations')}</div>
<div class="stat-value text-lg text-primary">{adventures.length}</div> <div class="stat-value text-lg text-primary">{locations.length}</div>
</div> </div>
</div> </div>
@ -418,7 +418,7 @@
{#if selectedEvent.extendedProps.adventureId} {#if selectedEvent.extendedProps.adventureId}
<a <a
href={`/adventures/${selectedEvent.extendedProps.adventureId}`} href={`/locations/${selectedEvent.extendedProps.adventureId}`}
class="btn btn-neutral btn-block mt-4" class="btn btn-neutral btn-block mt-4"
> >
{$t('map.view_details')} {$t('map.view_details')}

Some files were not shown because too many files have changed in this diff Show more