Full Code of Freika/dawarich for AI

master 13b663b2d7df cached
1530 files
21.4 MB
2.0M tokens
8919 symbols
1 requests
Download .txt
Showing preview only (8,101K chars total). Download the full file or copy to clipboard to get everything.
Repository: Freika/dawarich
Branch: master
Commit: 13b663b2d7df
Files: 1530
Total size: 21.4 MB

Directory structure:
gitextract_7c6y4j0y/

├── .app_version
├── .circleci/
│   └── config.yml
├── .devcontainer/
│   ├── Dockerfile
│   ├── devcontainer.json
│   └── docker-compose.yml
├── .dockerignore
├── .gitattributes
├── .github/
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   └── bug_report.md
│   ├── dependabot.yml
│   └── workflows/
│       ├── attach_compose.yml
│       ├── biome.yml
│       ├── build_and_push.yml
│       ├── ci.yml
│       ├── release_notifications.yml
│       └── rubocop.yml
├── .gitignore
├── .rspec
├── .rubocop.yml
├── .ruby-version
├── AGENTS.md
├── CHANGELOG.md
├── CLAUDE.md
├── CONTRIBUTING.md
├── DEVELOPMENT.md
├── Gemfile
├── LICENSE
├── Procfile
├── Procfile.dev
├── Procfile.production
├── Procfile.prometheus.dev
├── README.md
├── Rakefile
├── app/
│   ├── assets/
│   │   ├── builds/
│   │   │   ├── .keep
│   │   │   └── tailwind.css
│   │   ├── config/
│   │   │   └── manifest.js
│   │   ├── images/
│   │   │   ├── .keep
│   │   │   └── favicon/
│   │   │       └── browserconfig.xml.erb
│   │   └── stylesheets/
│   │       ├── actiontext.css
│   │       ├── application.css
│   │       ├── application.tailwind.css
│   │       ├── leaflet.control.layers.tree.css
│   │       ├── leaflet_theme.css
│   │       ├── maplibre-gl.css
│   │       ├── maps_maplibre.css
│   │       ├── maps_maplibre_panel.css
│   │       ├── maps_maplibre_replay.css
│   │       └── maps_maplibre_timeline_feed.css
│   ├── channels/
│   │   ├── application_cable/
│   │   │   ├── channel.rb
│   │   │   └── connection.rb
│   │   ├── family_locations_channel.rb
│   │   ├── imports_channel.rb
│   │   ├── notifications_channel.rb
│   │   ├── points_channel.rb
│   │   └── tracks_channel.rb
│   ├── controllers/
│   │   ├── api/
│   │   │   └── v1/
│   │   │       ├── areas_controller.rb
│   │   │       ├── countries/
│   │   │       │   ├── borders_controller.rb
│   │   │       │   └── visited_cities_controller.rb
│   │   │       ├── digests_controller.rb
│   │   │       ├── families/
│   │   │       │   └── locations_controller.rb
│   │   │       ├── health_controller.rb
│   │   │       ├── imports_controller.rb
│   │   │       ├── insights_controller.rb
│   │   │       ├── locations_controller.rb
│   │   │       ├── maps/
│   │   │       │   └── hexagons_controller.rb
│   │   │       ├── overland/
│   │   │       │   └── batches_controller.rb
│   │   │       ├── owntracks/
│   │   │       │   └── points_controller.rb
│   │   │       ├── photos_controller.rb
│   │   │       ├── places_controller.rb
│   │   │       ├── plan_controller.rb
│   │   │       ├── points/
│   │   │       │   └── tracked_months_controller.rb
│   │   │       ├── points_controller.rb
│   │   │       ├── settings_controller.rb
│   │   │       ├── stats_controller.rb
│   │   │       ├── subscriptions_controller.rb
│   │   │       ├── tags_controller.rb
│   │   │       ├── timeline_controller.rb
│   │   │       ├── tracks/
│   │   │       │   └── points_controller.rb
│   │   │       ├── tracks_controller.rb
│   │   │       ├── users_controller.rb
│   │   │       ├── visits/
│   │   │       │   └── possible_places_controller.rb
│   │   │       └── visits_controller.rb
│   │   ├── api_controller.rb
│   │   ├── application_controller.rb
│   │   ├── areas_controller.rb
│   │   ├── auth/
│   │   │   └── ios_controller.rb
│   │   ├── concerns/
│   │   │   ├── .keep
│   │   │   ├── flash_streamable.rb
│   │   │   ├── safe_timestamp_parser.rb
│   │   │   ├── sortable.rb
│   │   │   └── utm_trackable.rb
│   │   ├── exports_controller.rb
│   │   ├── families_controller.rb
│   │   ├── family/
│   │   │   ├── invitations_controller.rb
│   │   │   ├── location_requests_controller.rb
│   │   │   ├── location_sharing_controller.rb
│   │   │   └── memberships_controller.rb
│   │   ├── home_controller.rb
│   │   ├── imports_controller.rb
│   │   ├── insights_controller.rb
│   │   ├── map/
│   │   │   ├── leaflet_controller.rb
│   │   │   ├── maplibre_controller.rb
│   │   │   └── timeline_feeds_controller.rb
│   │   ├── metrics_controller.rb
│   │   ├── notifications_controller.rb
│   │   ├── places_controller.rb
│   │   ├── points_controller.rb
│   │   ├── settings/
│   │   │   ├── background_jobs_controller.rb
│   │   │   ├── general_controller.rb
│   │   │   ├── integrations_controller.rb
│   │   │   ├── maps_controller.rb
│   │   │   ├── onboardings_controller.rb
│   │   │   └── users_controller.rb
│   │   ├── settings_controller.rb
│   │   ├── shared/
│   │   │   ├── digests_controller.rb
│   │   │   └── stats_controller.rb
│   │   ├── stats_controller.rb
│   │   ├── tags_controller.rb
│   │   ├── trips_controller.rb
│   │   ├── users/
│   │   │   ├── digests_controller.rb
│   │   │   ├── omniauth_callbacks_controller.rb
│   │   │   ├── registrations_controller.rb
│   │   │   └── sessions_controller.rb
│   │   └── visits_controller.rb
│   ├── helpers/
│   │   ├── application_helper.rb
│   │   ├── country_flag_helper.rb
│   │   ├── datetime_formatting_helper.rb
│   │   ├── flash_helper.rb
│   │   ├── insights_helper.rb
│   │   ├── month_styling_helper.rb
│   │   ├── points_helper.rb
│   │   ├── stats_comparison_helper.rb
│   │   ├── stats_helper.rb
│   │   ├── tags_helper.rb
│   │   ├── trips_helper.rb
│   │   ├── user_helper.rb
│   │   └── users/
│   │       └── digests_helper.rb
│   ├── javascript/
│   │   ├── README.md
│   │   ├── application.js
│   │   ├── channels/
│   │   │   ├── consumer.js
│   │   │   ├── family_locations_channel.js
│   │   │   ├── imports_channel.js
│   │   │   ├── index.js
│   │   │   ├── notifications_channel.js
│   │   │   └── points_channel.js
│   │   ├── controllers/
│   │   │   ├── activity_heatmap_controller.js
│   │   │   ├── add_visit_controller.js
│   │   │   ├── application.js
│   │   │   ├── area_creation_v2_controller.js
│   │   │   ├── area_drawer_controller.js
│   │   │   ├── area_selector_controller.js
│   │   │   ├── base_controller.js
│   │   │   ├── checkbox_select_all_controller.js
│   │   │   ├── clipboard_controller.js
│   │   │   ├── color_picker_controller.js
│   │   │   ├── datetime_controller.js
│   │   │   ├── emoji_picker_controller.js
│   │   │   ├── family_members_controller.js
│   │   │   ├── family_navbar_indicator_controller.js
│   │   │   ├── flash_controller.js
│   │   │   ├── imports_controller.js
│   │   │   ├── index.js
│   │   │   ├── location_sharing_toggle_controller.js
│   │   │   ├── map_controls_controller.js
│   │   │   ├── map_panel_controller.js
│   │   │   ├── map_preview_controller.js
│   │   │   ├── maps/
│   │   │   │   ├── maplibre/
│   │   │   │   │   ├── area_selection_manager.js
│   │   │   │   │   ├── data_loader.js
│   │   │   │   │   ├── date_manager.js
│   │   │   │   │   ├── event_handlers.js
│   │   │   │   │   ├── filter_manager.js
│   │   │   │   │   ├── layer_manager.js
│   │   │   │   │   ├── map_data_manager.js
│   │   │   │   │   ├── map_initializer.js
│   │   │   │   │   ├── places_manager.js
│   │   │   │   │   ├── routes_manager.js
│   │   │   │   │   ├── settings_manager.js
│   │   │   │   │   └── visits_manager.js
│   │   │   │   ├── maplibre_controller.js
│   │   │   │   └── maplibre_realtime_controller.js
│   │   │   ├── maps_controller.js
│   │   │   ├── notifications_controller.js
│   │   │   ├── onboarding_modal_controller.js
│   │   │   ├── place_creation_controller.js
│   │   │   ├── places_filter_controller.js
│   │   │   ├── privacy_radius_controller.js
│   │   │   ├── public_stat_map_controller.js
│   │   │   ├── removals_controller.js
│   │   │   ├── sharing_modal_controller.js
│   │   │   ├── speed_color_editor_controller.js
│   │   │   ├── stat_page_controller.js
│   │   │   ├── timeline_feed_controller.js
│   │   │   ├── trip_map_controller.js
│   │   │   ├── trips_controller.js
│   │   │   ├── upload_controller.js
│   │   │   ├── visit_creation_v2_controller.js
│   │   │   ├── visit_modal_map_controller.js
│   │   │   ├── visit_modal_places_controller.js
│   │   │   ├── visit_name_controller.js
│   │   │   └── visits_map_controller.js
│   │   ├── maps/
│   │   │   ├── areas.js
│   │   │   ├── country_codes.js
│   │   │   ├── fog_of_war.js
│   │   │   ├── helpers.js
│   │   │   ├── layers.js
│   │   │   ├── live_map_handler.js
│   │   │   ├── location_search.js
│   │   │   ├── map_controls.js
│   │   │   ├── marker_factory.js
│   │   │   ├── markers.js
│   │   │   ├── photos.js
│   │   │   ├── places.js
│   │   │   ├── places_control.js
│   │   │   ├── polylines.js
│   │   │   ├── popups.js
│   │   │   ├── privacy_zones.js
│   │   │   ├── raster_maps_config.js
│   │   │   ├── scratch_layer.js
│   │   │   ├── theme_utils.js
│   │   │   ├── tracks.js
│   │   │   ├── vector_maps_config.js
│   │   │   └── visits.js
│   │   ├── maps_maplibre/
│   │   │   ├── channels/
│   │   │   │   └── map_channel.js
│   │   │   ├── components/
│   │   │   │   ├── photo_popup.js
│   │   │   │   ├── toast.js
│   │   │   │   ├── upgrade_banner.js
│   │   │   │   ├── visit_card.js
│   │   │   │   └── visit_popup.js
│   │   │   ├── layers/
│   │   │   │   ├── areas_layer.js
│   │   │   │   ├── base_layer.js
│   │   │   │   ├── family_layer.js
│   │   │   │   ├── fog_layer.js
│   │   │   │   ├── heatmap_layer.js
│   │   │   │   ├── photos_layer.js
│   │   │   │   ├── places_layer.js
│   │   │   │   ├── points_layer.js
│   │   │   │   ├── recent_point_layer.js
│   │   │   │   ├── replay_marker_layer.js
│   │   │   │   ├── routes_layer.js
│   │   │   │   ├── scratch_layer.js
│   │   │   │   ├── selected_points_layer.js
│   │   │   │   ├── selection_layer.js
│   │   │   │   ├── track_points_layer.js
│   │   │   │   ├── tracks_layer.js
│   │   │   │   └── visits_layer.js
│   │   │   ├── managers/
│   │   │   │   └── replay_manager.js
│   │   │   ├── services/
│   │   │   │   ├── api_client.js
│   │   │   │   └── location_search_service.js
│   │   │   └── utils/
│   │   │       ├── cleanup_helper.js
│   │   │       ├── fps_monitor.js
│   │   │       ├── geojson_transformers.js
│   │   │       ├── geometry.js
│   │   │       ├── layer_gate.js
│   │   │       ├── lazy_loader.js
│   │   │       ├── performance_monitor.js
│   │   │       ├── popup_theme.js
│   │   │       ├── progressive_loader.js
│   │   │       ├── route_segmenter.js
│   │   │       ├── search_manager.js
│   │   │       ├── settings_manager.js
│   │   │       ├── speed_colors.js
│   │   │       ├── style_manager.js
│   │   │       └── websocket_manager.js
│   │   ├── posthog.js
│   │   └── styles/
│   │       └── visits.css
│   ├── jobs/
│   │   ├── app_version_checking_job.rb
│   │   ├── application_job.rb
│   │   ├── area_visits_calculating_job.rb
│   │   ├── area_visits_calculation_scheduling_job.rb
│   │   ├── bulk_stats_calculating_job.rb
│   │   ├── bulk_visits_suggesting_job.rb
│   │   ├── cache/
│   │   │   ├── cleaning_job.rb
│   │   │   └── preheating_job.rb
│   │   ├── concerns/
│   │   │   └── user_timezone.rb
│   │   ├── data_migrations/
│   │   │   ├── backfill_country_name_job.rb
│   │   │   ├── backfill_motion_data_job.rb
│   │   │   ├── backfill_onboarding_completed_job.rb
│   │   │   ├── fix_route_opacity_job.rb
│   │   │   ├── migrate_places_lonlat_job.rb
│   │   │   ├── migrate_points_latlon_job.rb
│   │   │   ├── prefill_points_counter_cache_job.rb
│   │   │   ├── set_points_country_ids_job.rb
│   │   │   ├── set_reverse_geocoded_at_for_points_job.rb
│   │   │   └── start_settings_points_country_ids_job.rb
│   │   ├── enqueue_background_job.rb
│   │   ├── export_job.rb
│   │   ├── families/
│   │   │   └── expire_location_requests_job.rb
│   │   ├── family/
│   │   │   └── invitations/
│   │   │       ├── cleanup_job.rb
│   │   │       └── sending_job.rb
│   │   ├── import/
│   │   │   ├── google_takeout_job.rb
│   │   │   ├── immich_geodata_job.rb
│   │   │   ├── photoprism_geodata_job.rb
│   │   │   ├── process_job.rb
│   │   │   ├── update_points_count_job.rb
│   │   │   └── watcher_job.rb
│   │   ├── imports/
│   │   │   └── destroy_job.rb
│   │   ├── lite/
│   │   │   └── archival_warning_job.rb
│   │   ├── place_visits_calculating_job.rb
│   │   ├── places/
│   │   │   ├── bulk_name_fetching_job.rb
│   │   │   └── name_fetching_job.rb
│   │   ├── points/
│   │   │   ├── create_job.rb
│   │   │   ├── nightly_reverse_geocoding_job.rb
│   │   │   └── raw_data/
│   │   │       ├── archive_job.rb
│   │   │       └── re_archive_month_job.rb
│   │   ├── reverse_geocoding_job.rb
│   │   ├── stale_jobs_recovery_job.rb
│   │   ├── stats/
│   │   │   └── calculating_job.rb
│   │   ├── tracks/
│   │   │   ├── boundary_resolver_job.rb
│   │   │   ├── daily_generation_job.rb
│   │   │   ├── deduplication_job.rb
│   │   │   ├── parallel_generator_job.rb
│   │   │   ├── realtime_generation_job.rb
│   │   │   ├── recalculate_job.rb
│   │   │   ├── time_chunk_processor_job.rb
│   │   │   └── transportation_mode_recalculation_job.rb
│   │   ├── transportation_modes/
│   │   │   ├── backfill_job.rb
│   │   │   └── import_backfill_job.rb
│   │   ├── trips/
│   │   │   ├── calculate_all_job.rb
│   │   │   ├── calculate_countries_job.rb
│   │   │   ├── calculate_distance_job.rb
│   │   │   └── calculate_path_job.rb
│   │   ├── users/
│   │   │   ├── destroy_job.rb
│   │   │   ├── digests/
│   │   │   │   ├── calculating_job.rb
│   │   │   │   ├── email_sending_job.rb
│   │   │   │   └── year_end_scheduling_job.rb
│   │   │   ├── export_data_job.rb
│   │   │   ├── import_data_job.rb
│   │   │   ├── mailer_sending_job.rb
│   │   │   ├── recalculate_data_job.rb
│   │   │   └── trial_webhook_job.rb
│   │   └── visit_suggesting_job.rb
│   ├── mailers/
│   │   ├── application_mailer.rb
│   │   ├── family_mailer.rb
│   │   ├── users/
│   │   │   └── digests_mailer.rb
│   │   └── users_mailer.rb
│   ├── models/
│   │   ├── application_record.rb
│   │   ├── area.rb
│   │   ├── concerns/
│   │   │   ├── .keep
│   │   │   ├── archivable.rb
│   │   │   ├── calculateable.rb
│   │   │   ├── distance_convertible.rb
│   │   │   ├── distanceable.rb
│   │   │   ├── nearable.rb
│   │   │   ├── omniauthable.rb
│   │   │   ├── plan_scopable.rb
│   │   │   ├── point_validation.rb
│   │   │   ├── soft_deletable.rb
│   │   │   ├── taggable.rb
│   │   │   └── user_family.rb
│   │   ├── country.rb
│   │   ├── export.rb
│   │   ├── family/
│   │   │   ├── invitation.rb
│   │   │   ├── location_request.rb
│   │   │   └── membership.rb
│   │   ├── family.rb
│   │   ├── import.rb
│   │   ├── notification.rb
│   │   ├── place.rb
│   │   ├── place_visit.rb
│   │   ├── point.rb
│   │   ├── points/
│   │   │   └── raw_data_archive.rb
│   │   ├── stat.rb
│   │   ├── tag.rb
│   │   ├── tagging.rb
│   │   ├── track.rb
│   │   ├── track_segment.rb
│   │   ├── trip.rb
│   │   ├── user.rb
│   │   ├── users/
│   │   │   └── digest.rb
│   │   ├── visit.rb
│   │   └── visit_draft.rb
│   ├── policies/
│   │   ├── application_policy.rb
│   │   ├── family/
│   │   │   ├── invitation_policy.rb
│   │   │   └── membership_policy.rb
│   │   ├── family_policy.rb
│   │   ├── import_policy.rb
│   │   ├── insights_policy.rb
│   │   ├── place_policy.rb
│   │   └── tag_policy.rb
│   ├── queries/
│   │   ├── stats/
│   │   │   ├── daily_distance_query.rb
│   │   │   └── time_of_day_query.rb
│   │   ├── stats_query.rb
│   │   └── tracks/
│   │       └── index_query.rb
│   ├── serializers/
│   │   ├── api/
│   │   │   ├── digest_detail_serializer.rb
│   │   │   ├── digest_list_serializer.rb
│   │   │   ├── insights_details_serializer.rb
│   │   │   ├── insights_overview_serializer.rb
│   │   │   ├── location_search_result_serializer.rb
│   │   │   ├── photo_serializer.rb
│   │   │   ├── place_serializer.rb
│   │   │   ├── point_serializer.rb
│   │   │   ├── slim_point_serializer.rb
│   │   │   ├── user_serializer.rb
│   │   │   └── visit_serializer.rb
│   │   ├── export_serializer.rb
│   │   ├── exports/
│   │   │   ├── point_geojson_serializer.rb
│   │   │   └── point_gpx_serializer.rb
│   │   ├── point_serializer.rb
│   │   ├── points/
│   │   │   ├── geojson_serializer.rb
│   │   │   └── gpx_serializer.rb
│   │   ├── stats_serializer.rb
│   │   ├── tag_serializer.rb
│   │   ├── track_serializer.rb
│   │   ├── tracks/
│   │   │   └── geojson_serializer.rb
│   │   └── tracks_serializer.rb
│   ├── services/
│   │   ├── areas/
│   │   │   └── visits/
│   │   │       └── create.rb
│   │   ├── cache/
│   │   │   ├── clean.rb
│   │   │   ├── invalidate_user_caches.rb
│   │   │   └── preheat_insights_digests.rb
│   │   ├── check_app_version.rb
│   │   ├── concerns/
│   │   │   └── ssl_configurable.rb
│   │   ├── countries/
│   │   │   └── iso_code_mapper.rb
│   │   ├── countries_and_cities.rb
│   │   ├── exception_reporter.rb
│   │   ├── exports/
│   │   │   └── create.rb
│   │   ├── families/
│   │   │   ├── accept_invitation.rb
│   │   │   ├── create.rb
│   │   │   ├── create_location_request.rb
│   │   │   ├── invite.rb
│   │   │   ├── locations.rb
│   │   │   ├── memberships/
│   │   │   │   └── destroy.rb
│   │   │   └── update_location_sharing.rb
│   │   ├── geojson/
│   │   │   ├── importer.rb
│   │   │   └── params.rb
│   │   ├── google_maps/
│   │   │   ├── phone_takeout_importer.rb
│   │   │   ├── records_importer.rb
│   │   │   ├── records_storage_importer.rb
│   │   │   └── semantic_history_importer.rb
│   │   ├── gpx/
│   │   │   └── track_importer.rb
│   │   ├── immich/
│   │   │   ├── connection_tester.rb
│   │   │   ├── import_geodata.rb
│   │   │   ├── request_photos.rb
│   │   │   ├── response_analyzer.rb
│   │   │   └── response_validator.rb
│   │   ├── imports/
│   │   │   ├── broadcaster.rb
│   │   │   ├── create.rb
│   │   │   ├── destroy.rb
│   │   │   ├── file_loader.rb
│   │   │   ├── secure_file_downloader.rb
│   │   │   ├── source_detector.rb
│   │   │   └── watcher.rb
│   │   ├── insights/
│   │   │   ├── activity_heatmap_calculator.rb
│   │   │   ├── travel_insight_generator.rb
│   │   │   ├── travel_patterns_loader.rb
│   │   │   ├── year_comparison_calculator.rb
│   │   │   └── year_totals_calculator.rb
│   │   ├── jobs/
│   │   │   └── create.rb
│   │   ├── kml/
│   │   │   └── importer.rb
│   │   ├── location_search/
│   │   │   ├── geocoding_service.rb
│   │   │   ├── point_finder.rb
│   │   │   ├── result_aggregator.rb
│   │   │   └── spatial_matcher.rb
│   │   ├── maps/
│   │   │   ├── bounds_calculator.rb
│   │   │   ├── hexagon_center_manager.rb
│   │   │   ├── hexagon_polygon_generator.rb
│   │   │   └── hexagon_request_handler.rb
│   │   ├── metrics/
│   │   │   └── archives/
│   │   │       ├── compression_ratio.rb
│   │   │       ├── count_mismatch.rb
│   │   │       ├── operation.rb
│   │   │       ├── points_archived.rb
│   │   │       ├── size.rb
│   │   │       └── verification.rb
│   │   ├── notifications.rb
│   │   ├── overland/
│   │   │   ├── params.rb
│   │   │   └── points_creator.rb
│   │   ├── own_tracks/
│   │   │   ├── importer.rb
│   │   │   ├── params.rb
│   │   │   ├── point_creator.rb
│   │   │   └── rec_parser.rb
│   │   ├── photoprism/
│   │   │   ├── cache_preview_token.rb
│   │   │   ├── connection_tester.rb
│   │   │   ├── import_geodata.rb
│   │   │   ├── request_photos.rb
│   │   │   └── response_validator.rb
│   │   ├── photos/
│   │   │   ├── cache_cleaner.rb
│   │   │   ├── importer.rb
│   │   │   ├── search.rb
│   │   │   └── thumbnail.rb
│   │   ├── places/
│   │   │   ├── name_fetcher.rb
│   │   │   ├── nearby_search.rb
│   │   │   └── visits/
│   │   │       └── create.rb
│   │   ├── points/
│   │   │   ├── create.rb
│   │   │   ├── live_broadcaster.rb
│   │   │   ├── motion_data_extractor.rb
│   │   │   ├── params.rb
│   │   │   ├── raw_data/
│   │   │   │   ├── archiver.rb
│   │   │   │   ├── chunk_compressor.rb
│   │   │   │   ├── clearer.rb
│   │   │   │   ├── encryption.rb
│   │   │   │   ├── restorer.rb
│   │   │   │   └── verifier.rb
│   │   │   └── raw_data_lonlat_extractor.rb
│   │   ├── points_limit_exceeded.rb
│   │   ├── prometheus_metrics.rb
│   │   ├── reverse_geocoding/
│   │   │   ├── places/
│   │   │   │   └── fetch_data.rb
│   │   │   └── points/
│   │   │       └── fetch_data.rb
│   │   ├── settings/
│   │   │   └── update.rb
│   │   ├── stats/
│   │   │   ├── bulk_calculator.rb
│   │   │   ├── calculate_month.rb
│   │   │   └── hexagon_calculator.rb
│   │   ├── subscription/
│   │   │   ├── decode_jwt_token.rb
│   │   │   └── encode_jwt_token.rb
│   │   ├── supporter/
│   │   │   └── verify_email.rb
│   │   ├── tasks/
│   │   │   └── imports/
│   │   │       └── google_records.rb
│   │   ├── timeline/
│   │   │   └── day_assembler.rb
│   │   ├── tracks/
│   │   │   ├── boundary_detector.rb
│   │   │   ├── build_path.rb
│   │   │   ├── deduplicator.rb
│   │   │   ├── incremental_generator.rb
│   │   │   ├── merger.rb
│   │   │   ├── parallel_generator.rb
│   │   │   ├── realtime_debouncer.rb
│   │   │   ├── reprocessor.rb
│   │   │   ├── segmentation.rb
│   │   │   ├── session_manager.rb
│   │   │   ├── time_chunker.rb
│   │   │   ├── track_builder.rb
│   │   │   └── transportation_recalculation_status.rb
│   │   ├── transportation_modes/
│   │   │   ├── activity_backfiller.rb
│   │   │   ├── detector.rb
│   │   │   ├── mode_classifier.rb
│   │   │   ├── movement_analyzer.rb
│   │   │   └── source_data_extractor.rb
│   │   ├── trips/
│   │   │   └── photos.rb
│   │   ├── users/
│   │   │   ├── destroy.rb
│   │   │   ├── digests/
│   │   │   │   ├── activity_breakdown_calculator.rb
│   │   │   │   ├── calculate_month.rb
│   │   │   │   ├── calculate_year.rb
│   │   │   │   ├── first_time_visits_calculator.rb
│   │   │   │   ├── month_over_month_calculator.rb
│   │   │   │   ├── monthly_first_time_visits_calculator.rb
│   │   │   │   ├── seasonality_calculator.rb
│   │   │   │   └── year_over_year_calculator.rb
│   │   │   ├── export_data/
│   │   │   │   ├── areas.rb
│   │   │   │   ├── digests.rb
│   │   │   │   ├── exports.rb
│   │   │   │   ├── imports.rb
│   │   │   │   ├── notifications.rb
│   │   │   │   ├── places.rb
│   │   │   │   ├── points.rb
│   │   │   │   ├── stats.rb
│   │   │   │   ├── tracks.rb
│   │   │   │   ├── trips.rb
│   │   │   │   └── visits.rb
│   │   │   ├── export_data.rb
│   │   │   ├── import_data/
│   │   │   │   ├── areas.rb
│   │   │   │   ├── digests.rb
│   │   │   │   ├── exports.rb
│   │   │   │   ├── imports.rb
│   │   │   │   ├── notifications.rb
│   │   │   │   ├── places.rb
│   │   │   │   ├── points.rb
│   │   │   │   ├── raw_data_archives.rb
│   │   │   │   ├── settings.rb
│   │   │   │   ├── stats.rb
│   │   │   │   ├── taggings.rb
│   │   │   │   ├── tags.rb
│   │   │   │   ├── tracks.rb
│   │   │   │   ├── trips.rb
│   │   │   │   ├── v1_handler.rb
│   │   │   │   ├── v2_handler.rb
│   │   │   │   └── visits.rb
│   │   │   ├── import_data.rb
│   │   │   ├── safe_settings.rb
│   │   │   └── transportation_thresholds_updater.rb
│   │   └── visits/
│   │       ├── bulk_update.rb
│   │       ├── create.rb
│   │       ├── creator.rb
│   │       ├── detector.rb
│   │       ├── find_in_time.rb
│   │       ├── find_within_bounding_box.rb
│   │       ├── finder.rb
│   │       ├── group.rb
│   │       ├── merge_service.rb
│   │       ├── merger.rb
│   │       ├── names/
│   │       │   ├── builder.rb
│   │       │   ├── fetcher.rb
│   │       │   └── suggester.rb
│   │       ├── place_finder.rb
│   │       ├── smart_detect.rb
│   │       ├── suggest.rb
│   │       └── time_chunks.rb
│   └── views/
│       ├── active_storage/
│       │   └── blobs/
│       │       └── _blob.html.erb
│       ├── application/
│       │   └── _favicon.html.erb
│       ├── devise/
│       │   ├── confirmations/
│       │   │   └── new.html.erb
│       │   ├── mailer/
│       │   │   ├── confirmation_instructions.html.erb
│       │   │   ├── email_changed.html.erb
│       │   │   ├── password_change.html.erb
│       │   │   ├── reset_password_instructions.html.erb
│       │   │   └── unlock_instructions.html.erb
│       │   ├── passwords/
│       │   │   ├── edit.html.erb
│       │   │   └── new.html.erb
│       │   ├── registrations/
│       │   │   ├── _api_key.html.erb
│       │   │   ├── _points_usage.html.erb
│       │   │   ├── edit.html.erb
│       │   │   └── new.html.erb
│       │   ├── sessions/
│       │   │   └── new.html.erb
│       │   ├── shared/
│       │   │   ├── _error_messages.html.erb
│       │   │   └── _links.html.erb
│       │   └── unlocks/
│       │       └── new.html.erb
│       ├── exports/
│       │   ├── _table_row.html.erb
│       │   └── index.html.erb
│       ├── families/
│       │   ├── _location_sharing_toggle.html.erb
│       │   ├── _navbar_indicator.html.erb
│       │   ├── edit.html.erb
│       │   ├── index.html.erb
│       │   ├── new.html.erb
│       │   └── show.html.erb
│       ├── family/
│       │   ├── invitations/
│       │   │   ├── index.html.erb
│       │   │   └── show.html.erb
│       │   └── location_requests/
│       │       └── show.html.erb
│       ├── family_mailer/
│       │   ├── invitation.html.erb
│       │   ├── invitation.text.erb
│       │   ├── location_request.html.erb
│       │   ├── location_request.text.erb
│       │   ├── member_joined.html.erb
│       │   └── member_joined.text.erb
│       ├── home/
│       │   └── index.html.erb
│       ├── imports/
│       │   ├── _form.html.erb
│       │   ├── _import.html.erb
│       │   ├── _table_row.html.erb
│       │   ├── destroy.turbo_stream.erb
│       │   ├── edit.html.erb
│       │   ├── index.html.erb
│       │   ├── new.html.erb
│       │   └── show.html.erb
│       ├── insights/
│       │   ├── _activity_breakdown.html.erb
│       │   ├── _activity_heatmap.html.erb
│       │   ├── _activity_heatmap_cells.html.erb
│       │   ├── _activity_streak.html.erb
│       │   ├── _details_skeleton.html.erb
│       │   ├── _header.html.erb
│       │   ├── _location_clusters.html.erb
│       │   ├── _monthly_digest.html.erb
│       │   ├── _movement_wellness.html.erb
│       │   ├── _pro_locked_card.html.erb
│       │   ├── _stats_row.html.erb
│       │   ├── _top_visited_locations.html.erb
│       │   ├── _travel_patterns.html.erb
│       │   ├── _travel_story.html.erb
│       │   ├── _year_comparison.html.erb
│       │   ├── details.html.erb
│       │   └── index.html.erb
│       ├── kaminari/
│       │   ├── _first_page.html.erb
│       │   ├── _gap.html.erb
│       │   ├── _last_page.html.erb
│       │   ├── _next_page.html.erb
│       │   ├── _page.html.erb
│       │   ├── _paginator.html.erb
│       │   └── _prev_page.html.erb
│       ├── layouts/
│       │   ├── action_text/
│       │   │   └── contents/
│       │   │       └── _content.html.erb
│       │   ├── application.html.erb
│       │   ├── mailer.html.erb
│       │   ├── mailer.text.erb
│       │   └── map.html.erb
│       ├── map/
│       │   ├── _onboarding_modal.html.erb
│       │   ├── leaflet/
│       │   │   ├── _settings_modals.html.erb
│       │   │   └── index.html.erb
│       │   ├── maplibre/
│       │   │   ├── _area_creation_modal.html.erb
│       │   │   ├── _replay_panel.html.erb
│       │   │   ├── _settings_panel.html.erb
│       │   │   ├── _visit_creation_modal.html.erb
│       │   │   ├── _webgl_error.html.erb
│       │   │   └── index.html.erb
│       │   └── timeline_feeds/
│       │       ├── _day.html.erb
│       │       ├── _day_summary.html.erb
│       │       ├── _feed.html.erb
│       │       ├── _journey_entry.html.erb
│       │       ├── _track_info.html.erb
│       │       ├── _visit_entry.html.erb
│       │       ├── index.html.erb
│       │       └── track_info.html.erb
│       ├── notifications/
│       │   ├── _badge.html.erb
│       │   ├── _navbar_item.html.erb
│       │   ├── _notification.html.erb
│       │   ├── index.html.erb
│       │   └── show.html.erb
│       ├── places/
│       │   ├── _nearby_places.html.erb
│       │   └── index.html.erb
│       ├── points/
│       │   ├── _point.html.erb
│       │   └── index.html.erb
│       ├── settings/
│       │   ├── _navigation.html.erb
│       │   ├── background_jobs/
│       │   │   └── index.html.erb
│       │   ├── general/
│       │   │   ├── _supporter_status.html.erb
│       │   │   └── index.html.erb
│       │   ├── integrations/
│       │   │   └── index.html.erb
│       │   ├── maps/
│       │   │   └── index.html.erb
│       │   ├── subscriptions/
│       │   │   └── index.html.erb
│       │   └── users/
│       │       ├── edit.html.erb
│       │       ├── index.html.erb
│       │       └── show.html.erb
│       ├── shared/
│       │   ├── _chartkick_scripts.html.erb
│       │   ├── _flash.html.erb
│       │   ├── _flash_message.html.erb
│       │   ├── _footer.html.erb
│       │   ├── _legal_footer.html.erb
│       │   ├── _navbar.html.erb
│       │   ├── _place_creation_modal.html.erb
│       │   ├── _plan_data_window_alert.html.erb
│       │   ├── _sharing_link.html.erb
│       │   ├── _sharing_modal.html.erb
│       │   ├── _trix_scripts.html.erb
│       │   ├── map/
│       │   │   ├── _date_navigation.html.erb
│       │   │   ├── _date_navigation_v2.html.erb
│       │   │   └── _upgrade_banner.html.erb
│       │   └── navbar/
│       │       ├── _help_links.html.erb
│       │       └── _theme_toggle.html.erb
│       ├── stats/
│       │   ├── _locked_year_card.html.erb
│       │   ├── _month.html.erb
│       │   ├── _reverse_geocoding_stats.html.erb
│       │   ├── _stat.html.erb
│       │   ├── _year.html.erb
│       │   ├── index.html.erb
│       │   ├── month.html.erb
│       │   ├── public_month.html.erb
│       │   └── show.html.erb
│       ├── tags/
│       │   ├── _form.html.erb
│       │   ├── edit.html.erb
│       │   ├── index.html.erb
│       │   └── new.html.erb
│       ├── trips/
│       │   ├── _countries.html.erb
│       │   ├── _distance.html.erb
│       │   ├── _form.html.erb
│       │   ├── _path.html.erb
│       │   ├── _trip.html.erb
│       │   ├── edit.html.erb
│       │   ├── index.html.erb
│       │   ├── new.html.erb
│       │   └── show.html.erb
│       ├── users/
│       │   ├── digests/
│       │   │   ├── index.html.erb
│       │   │   ├── public_year.html.erb
│       │   │   └── show.html.erb
│       │   └── digests_mailer/
│       │       ├── year_end_digest.html.erb
│       │       └── year_end_digest.text.erb
│       ├── users_mailer/
│       │   ├── archival_approaching.html.erb
│       │   ├── archival_approaching.text.erb
│       │   ├── explore_features.html.erb
│       │   ├── explore_features.text.erb
│       │   ├── post_trial_reminder_early.html.erb
│       │   ├── post_trial_reminder_early.text.erb
│       │   ├── post_trial_reminder_late.html.erb
│       │   ├── post_trial_reminder_late.text.erb
│       │   ├── trial_expired.html.erb
│       │   ├── trial_expired.text.erb
│       │   ├── trial_expires_soon.html.erb
│       │   ├── trial_expires_soon.text.erb
│       │   ├── welcome.html.erb
│       │   └── welcome.text.erb
│       └── visits/
│           ├── _buttons.html.erb
│           ├── _modal.html.erb
│           ├── _name.html.erb
│           ├── _visit.html.erb
│           └── index.html.erb
├── app.json
├── bin/
│   ├── dev
│   ├── importmap
│   ├── rails
│   ├── rake
│   ├── rubocop
│   └── setup
├── biome.json
├── config/
│   ├── application.rb
│   ├── boot.rb
│   ├── cable.yml
│   ├── database.ci.yml
│   ├── database.yml
│   ├── environment.rb
│   ├── environments/
│   │   ├── development.rb
│   │   ├── production.rb
│   │   ├── staging.rb
│   │   └── test.rb
│   ├── favicon.json
│   ├── importmap.rb
│   ├── initializers/
│   │   ├── 00_random.rb
│   │   ├── 01_constants.rb
│   │   ├── 03_dawarich_settings.rb
│   │   ├── assets.rb
│   │   ├── aws.rb
│   │   ├── cache_jobs.rb
│   │   ├── content_security_policy.rb
│   │   ├── devise.rb
│   │   ├── dns_cache.rb
│   │   ├── filter_parameter_logging.rb
│   │   ├── geocoder.rb
│   │   ├── httparty.rb
│   │   ├── inflections.rb
│   │   ├── mime_types.rb
│   │   ├── new_framework_defaults_8_0.rb
│   │   ├── oj.rb
│   │   ├── permissions_policy.rb
│   │   ├── prometheus.rb
│   │   ├── rack_attack.rb
│   │   ├── rails_icons.rb
│   │   ├── rails_pulse.rb
│   │   ├── rswag_api.rb
│   │   ├── rswag_ui.rb
│   │   ├── sentry.rb
│   │   ├── sidekiq.rb
│   │   └── web_app_manifest.rb
│   ├── locales/
│   │   ├── devise.en.yml
│   │   └── en.yml
│   ├── puma.rb
│   ├── routes.rb
│   ├── schedule.yml
│   ├── sidekiq.yml
│   ├── storage.yml
│   └── tailwind.config.js
├── config.ru
├── db/
│   ├── data/
│   │   ├── 20240525110530_bind_existing_points_to_first_user.rb
│   │   ├── 20240610170930_remove_points_without_coordinates.rb
│   │   ├── 20240625201842_add_fog_of_war_meters_to_settings.rb
│   │   ├── 20240713103122_make_first_user_admin.rb
│   │   ├── 20240724141417_add_visit_settings_to_user.rb
│   │   ├── 20240730130922_add_route_opacity_to_settings.rb
│   │   ├── 20240808133112_run_initial_visit_suggestion.rb
│   │   ├── 20240815174852_add_owntracks_points_data.rb
│   │   ├── 20240822094532_add_counter_cache_to_imports.rb
│   │   ├── 20241022100309_add_points_rendering_mode_to_settings.rb
│   │   ├── 20241107112451_add_live_map_enabled_to_settings.rb
│   │   ├── 20241202125248_set_reverse_geocoded_at_for_points.rb
│   │   ├── 20241206163450_create_telemetry_notification.rb
│   │   ├── 20250104204852_create_photon_load_notification.rb
│   │   ├── 20250120154554_remove_duplicate_points.rb
│   │   ├── 20250123151849_create_paths_for_trips.rb
│   │   ├── 20250222213848_migrate_points_latlon.rb
│   │   ├── 20250226192005_activate_selfhosted_users.rb
│   │   ├── 20250303194123_migrate_places_lonlat.rb
│   │   ├── 20250403204658_update_imports_points_count.rb
│   │   ├── 20250404182629_set_active_until_for_selfhosted_users.rb
│   │   ├── 20250516180933_set_points_country_ids.rb
│   │   ├── 20250518173936_fix_france_codes.rb
│   │   ├── 20250518174305_set_default_distance_unit_for_user.rb
│   │   ├── 20250704185707_create_tracks_from_points.rb
│   │   ├── 20250709195003_recalculate_trips_distance.rb
│   │   └── 20250720171241_recalculate_stats_after_changing_distance_units.rb
│   ├── data_schema.rb
│   ├── migrate/
│   │   ├── 20220325100310_devise_create_users.rb
│   │   ├── 20231021104256_add_service_name_to_active_storage_blobs.active_storage.rb
│   │   ├── 20231021104257_create_active_storage_variant_records.active_storage.rb
│   │   ├── 20231021104258_remove_not_null_on_active_storage_blobs_checksum.active_storage.rb
│   │   ├── 20240315213523_create_points.rb
│   │   ├── 20240315215423_create_imports.rb
│   │   ├── 20240317171559_add_indicies_to_points_latitude_longitude.rb
│   │   ├── 20240323125126_add_raw_points_and_doubles_to_import.rb
│   │   ├── 20240323160300_create_stats.rb
│   │   ├── 20240323161049_add_index_to_points_timestamp.rb
│   │   ├── 20240323190039_add_user_id_to_stat.rb
│   │   ├── 20240324161309_create_active_storage_tables.active_storage.rb
│   │   ├── 20240324161800_add_processed_to_imports.rb
│   │   ├── 20240324173315_add_daily_distance_to_stat.rb
│   │   ├── 20240404154959_add_api_key_to_users.rb
│   │   ├── 20240425200155_add_raw_data_to_imports.rb
│   │   ├── 20240518095848_add_theme_to_users.rb
│   │   ├── 20240525110244_add_user_id_to_points.rb
│   │   ├── 20240612152451_create_exports.rb
│   │   ├── 20240620205120_add_settings_to_users.rb
│   │   ├── 20240630093005_add_fog_of_war_to_default_settings.rb
│   │   ├── 20240703105734_create_notifications.rb
│   │   ├── 20240712141303_add_geodata_to_points.rb
│   │   ├── 20240713103051_add_admin_to_users.rb
│   │   ├── 20240721165313_create_areas.rb
│   │   ├── 20240721183005_create_visits.rb
│   │   ├── 20240721183116_add_visit_id_to_points.rb
│   │   ├── 20240805150111_create_places.rb
│   │   ├── 20240808102348_add_place_id_to_visits.rb
│   │   ├── 20240808102425_make_area_id_optional_in_visits.rb
│   │   ├── 20240808121027_create_place_visits.rb
│   │   ├── 20240822092405_add_points_count_to_imports.rb
│   │   ├── 20241127161621_create_trips.rb
│   │   ├── 20241128095325_create_action_text_tables.action_text.rb
│   │   ├── 20241202114820_add_reverse_geocoded_at_to_points.rb
│   │   ├── 20241205160055_add_devise_trackable_columns_to_users.rb
│   │   ├── 20241211113119_add_started_at_index_to_visits.rb
│   │   ├── 20241226202204_add_database_users_constraints.rb
│   │   ├── 20241226202831_validate_add_database_users_constraints.rb
│   │   ├── 20250120152014_add_course_and_course_accuracy_to_points.rb
│   │   ├── 20250120152540_add_external_track_id_to_points.rb
│   │   ├── 20250120154555_add_unique_index_to_points.rb
│   │   ├── 20250123145155_enable_postgis_extension.rb
│   │   ├── 20250123151657_add_path_to_trips.rb
│   │   ├── 20250219195822_add_status_to_users.rb
│   │   ├── 20250221181805_add_lonlat_to_points.rb
│   │   ├── 20250221185032_add_lonlat_index.rb
│   │   ├── 20250221194430_remove_points_latitude_longitude_uniqueness_index.rb
│   │   ├── 20250221194509_add_unique_lon_lat_index_to_points.rb
│   │   ├── 20250303194009_add_lonlat_to_places.rb
│   │   ├── 20250303194043_add_lonlat_index_to_places.rb
│   │   ├── 20250324180755_add_format_start_at_end_at_to_exports.rb
│   │   ├── 20250404182437_add_active_until_to_users.rb
│   │   ├── 20250513164521_add_visited_countries_to_trips.rb
│   │   ├── 20250515190752_create_countries.rb
│   │   ├── 20250515192211_add_country_id_to_points.rb
│   │   ├── 20250625185030_add_file_type_to_exports.rb
│   │   ├── 20250627184017_add_status_to_imports.rb
│   │   ├── 20250703193656_create_tracks.rb
│   │   ├── 20250703193657_add_track_id_to_points.rb
│   │   ├── 20250721204404_add_index_on_places_geodata_osm_id.rb
│   │   ├── 20250723164055_add_track_generation_composite_index.rb
│   │   ├── 20250728191359_add_country_name_to_points.rb
│   │   ├── 20250821192219_add_points_count_to_users.rb
│   │   ├── 20250823125940_remove_default_from_imports_source.rb
│   │   ├── 20250905120121_add_user_country_composite_index_to_points.rb
│   │   ├── 20250910224538_add_sharing_fields_to_stats.rb
│   │   ├── 20250910224714_add_index_to_stats_share_uuid.rb
│   │   ├── 20250918215512_add_h3_hex_ids_to_stats.rb
│   │   ├── 20250926220114_create_families.rb
│   │   ├── 20250926220135_create_family_memberships.rb
│   │   ├── 20250926220158_create_family_invitations.rb
│   │   ├── 20250926220345_validate_family_foreign_keys.rb
│   │   ├── 20251028130433_add_omniauth_to_users.rb
│   │   ├── 20251030190924_add_utm_parameters_to_users.rb
│   │   ├── 20251116184506_add_user_id_to_places.rb
│   │   ├── 20251116184514_create_tags.rb
│   │   ├── 20251116184520_create_taggings.rb
│   │   ├── 20251118204141_add_privacy_radius_to_tags.rb
│   │   ├── 20251118210506_add_note_to_places.rb
│   │   ├── 20251201192510_add_user_id_reverse_geocoded_at_index_to_points.rb
│   │   ├── 20251206000001_create_points_raw_data_archives.rb
│   │   ├── 20251206000002_add_archival_columns_to_points.rb
│   │   ├── 20251206000004_validate_archival_foreign_keys.rb
│   │   ├── 20251208210410_add_composite_index_to_stats.rb
│   │   ├── 20251210193532_add_verified_at_to_points_raw_data_archives.rb
│   │   ├── 20251226170919_add_composite_index_to_points_user_id_timestamp.rb
│   │   ├── 20251227000001_create_digests.rb
│   │   ├── 20251227223614_change_digests_distance_to_bigint.rb
│   │   ├── 20251228000000_remove_unused_indexes.rb
│   │   ├── 20251228100000_add_performance_indexes.rb
│   │   ├── 20251228163703_install_rails_pulse_tables.rb
│   │   ├── 20260103114630_add_indexes_to_points_for_stats_query.rb
│   │   ├── 20260108192905_add_deleted_at_to_users.rb
│   │   ├── 20260112192240_set_existing_users_to_map_v1.rb
│   │   ├── 20260113230537_set_points_timestamp_from_geojson_date.rb
│   │   ├── 20260120193124_add_month_to_digests.rb
│   │   ├── 20260120193200_create_track_segments.rb
│   │   ├── 20260120193336_add_dominant_mode_to_tracks.rb
│   │   ├── 20260120193401_add_travel_patterns_to_digests.rb
│   │   ├── 20260120193501_change_tracks_distance_precision.rb
│   │   ├── 20260124221434_add_index_to_track_segments.rb
│   │   ├── 20260125100000_enqueue_transportation_mode_backfill_jobs.rb
│   │   ├── 20260201000001_add_processing_started_at_to_exports_and_imports.rb
│   │   ├── 20260201000002_add_error_message_to_exports.rb
│   │   ├── 20260206202634_deduplicate_tracks.rb
│   │   ├── 20260216190000_add_unique_index_to_raw_data_archives.rb
│   │   ├── 20260217000000_optimize_points_indexes.rb
│   │   ├── 20260217000001_backfill_motion_data_from_raw_data.rb
│   │   ├── 20260222215414_add_error_message_to_imports.rb
│   │   ├── 20260301201446_add_plan_to_users.rb
│   │   ├── 20260301202147_set_plan_for_existing_users.rb
│   │   ├── 20260310000001_drop_redundant_indexes.rb
│   │   ├── 20260310000002_add_composite_indexes_and_drop_low_selectivity.rb
│   │   ├── 20260310000003_add_unique_index_to_place_visits.rb
│   │   ├── 20260310000006_fix_tracks_original_path_srid.rb
│   │   ├── 20260313134546_create_family_location_requests.rb
│   │   ├── 20260314000001_fix_route_opacity_default.rb
│   │   └── 20260315000001_backfill_onboarding_completed_for_existing_users.rb
│   ├── rails_pulse_migrate/
│   │   └── .keep
│   ├── rails_pulse_schema.rb
│   ├── schema.rb
│   └── seeds.rb
├── docker/
│   ├── Dockerfile
│   ├── docker-compose.yml
│   ├── sidekiq-entrypoint.sh
│   └── web-entrypoint.sh
├── docs/
│   ├── How_to_extract_geodata_from_photos.md
│   ├── How_to_install_Dawarich_in_k8s.md
│   ├── How_to_install_Dawarich_on_Synology.md
│   ├── How_to_install_Dawarich_using_Docker.md
│   ├── how_to_setup_reverse_proxy.md
│   └── synology/
│       ├── docker-compose.yml
│       ├── spk.tgz
│       └── update.sh
├── e2e/
│   ├── README.md
│   ├── helpers/
│   │   ├── map.js
│   │   ├── navigation.js
│   │   ├── places.js
│   │   └── selection.js
│   ├── lite/
│   │   └── plan-gates.spec.js
│   ├── map/
│   │   ├── map-add-visit.spec.js
│   │   ├── map-bulk-delete.spec.js
│   │   ├── map-calendar-panel.spec.js
│   │   ├── map-controls.spec.js
│   │   ├── map-info-toggle.spec.js
│   │   ├── map-layers.spec.js
│   │   ├── map-places-creation.spec.js
│   │   ├── map-places-layers.spec.js
│   │   ├── map-points.spec.js
│   │   ├── map-route-interactions.spec.js
│   │   ├── map-routes-tracks-selector.spec.js
│   │   ├── map-search.spec.js
│   │   ├── map-selection-tool.spec.js
│   │   ├── map-settings-panel.spec.js
│   │   ├── map-side-panel.spec.js
│   │   ├── map-stats-display.spec.js
│   │   ├── map-suggested-visits.spec.js
│   │   └── map-visits.spec.js
│   ├── setup/
│   │   ├── auth-lite.setup.js
│   │   └── auth.setup.js
│   └── v2/
│       ├── helpers/
│       │   ├── api.js
│       │   ├── constants.js
│       │   └── setup.js
│       ├── map/
│       │   ├── area-selection.spec.js
│       │   ├── core.spec.js
│       │   ├── interactions.spec.js
│       │   ├── layers/
│       │   │   ├── advanced.spec.js
│       │   │   ├── areas.spec.js
│       │   │   ├── family.spec.js
│       │   │   ├── heatmap.spec.js
│       │   │   ├── photos.spec.js
│       │   │   ├── places.spec.js
│       │   │   ├── points.spec.js
│       │   │   ├── routes.spec.js
│       │   │   ├── track-segments.spec.js
│       │   │   ├── tracks.spec.js
│       │   │   └── visits.spec.js
│       │   ├── navigation.spec.js
│       │   ├── performance.spec.js
│       │   ├── replay.spec.js
│       │   ├── search.spec.js
│       │   ├── settings.spec.js
│       │   └── timeline-feed.spec.js
│       ├── realtime/
│       │   ├── family.spec.js
│       │   ├── live-mode-api.spec.js
│       │   └── live-mode.spec.js
│       └── trips.spec.js
├── lib/
│   ├── assets/
│   │   ├── .keep
│   │   └── countries.geojson
│   ├── json_stream_handler.rb
│   ├── tasks/
│   │   ├── .keep
│   │   ├── data_cleanup.rake
│   │   ├── demo.rake
│   │   ├── exports.rake
│   │   ├── import.rake
│   │   ├── imports.rake
│   │   ├── points.rake
│   │   ├── points_raw_data.rake
│   │   ├── rswag.rake
│   │   ├── users.rake
│   │   └── webmanifest.rake
│   └── timestamps.rb
├── log/
│   └── .keep
├── package.json
├── playwright.config.js
├── public/
│   ├── .well-known/
│   │   └── apple-app-site-association
│   ├── 400.html
│   ├── 404.html
│   ├── 406-unsupported-browser.html
│   ├── 422.html
│   ├── 500.html
│   ├── exports/
│   │   └── .keep
│   ├── maps_maplibre/
│   │   └── styles/
│   │       ├── black.json
│   │       ├── dark.json
│   │       ├── grayscale.json
│   │       ├── light.json
│   │       └── white.json
│   ├── robots.txt
│   └── site.webmanifest
├── spec/
│   ├── channels/
│   │   ├── imports_channel_spec.rb
│   │   ├── notifications_channel_spec.rb
│   │   ├── points_channel_spec.rb
│   │   └── tracks_channel_spec.rb
│   ├── controllers/
│   │   ├── api_controller_spec.rb
│   │   ├── application_controller_spec.rb
│   │   └── concerns/
│   │       └── safe_timestamp_parser_spec.rb
│   ├── factories/
│   │   ├── areas.rb
│   │   ├── countries.rb
│   │   ├── exports.rb
│   │   ├── families.rb
│   │   ├── family_invitations.rb
│   │   ├── family_location_requests.rb
│   │   ├── family_memberships.rb
│   │   ├── imports.rb
│   │   ├── notifications.rb
│   │   ├── place_visits.rb
│   │   ├── places.rb
│   │   ├── points.rb
│   │   ├── points_raw_data_archives.rb
│   │   ├── stats.rb
│   │   ├── taggings.rb
│   │   ├── tags.rb
│   │   ├── track_segments.rb
│   │   ├── tracks.rb
│   │   ├── trips.rb
│   │   ├── users/
│   │   │   └── digests.rb
│   │   ├── users.rb
│   │   └── visits.rb
│   ├── fixtures/
│   │   ├── files/
│   │   │   ├── geojson/
│   │   │   │   ├── export.json
│   │   │   │   ├── export_same_points.json
│   │   │   │   ├── google_takeout_example.json
│   │   │   │   └── gpslogger_example.json
│   │   │   ├── google/
│   │   │   │   ├── location-history/
│   │   │   │   │   ├── with_activitySegment_with_startLocation.json
│   │   │   │   │   ├── with_activitySegment_with_startLocation_timestampMs.json
│   │   │   │   │   ├── with_activitySegment_with_startLocation_timestamp_in_milliseconds_format.json
│   │   │   │   │   ├── with_activitySegment_with_startLocation_timestamp_in_seconds_format.json
│   │   │   │   │   ├── with_activitySegment_with_startLocation_with_iso_timestamp.json
│   │   │   │   │   ├── with_activitySegment_without_startLocation.json
│   │   │   │   │   ├── with_activitySegment_without_startLocation_without_waypointPath.json
│   │   │   │   │   ├── with_placeVisit_with_location_with_coordinates.json
│   │   │   │   │   ├── with_placeVisit_with_location_with_coordinates_with_iso_timestamp.json
│   │   │   │   │   ├── with_placeVisit_with_location_with_coordinates_with_milliseconds_timestamp.json
│   │   │   │   │   ├── with_placeVisit_with_location_with_coordinates_with_seconds_timestamp.json
│   │   │   │   │   ├── with_placeVisit_with_location_with_coordinates_with_timestampMs.json
│   │   │   │   │   ├── with_placeVisit_without_location_with_coordinates.json
│   │   │   │   │   └── with_placeVisit_without_location_with_coordinates_with_otherCandidateLocations.json
│   │   │   │   ├── location-history.json
│   │   │   │   ├── phone-takeout_w_3_duplicates.json
│   │   │   │   ├── records.json
│   │   │   │   └── semantic_history.json
│   │   │   ├── gpx/
│   │   │   │   ├── arc_example.gpx
│   │   │   │   ├── garmin_example.gpx
│   │   │   │   ├── gpx_track_multiple_segments.gpx
│   │   │   │   ├── gpx_track_multiple_tracks.gpx
│   │   │   │   └── gpx_track_single_segment.gpx
│   │   │   ├── immich/
│   │   │   │   ├── geodata.json
│   │   │   │   └── response.json
│   │   │   ├── kml/
│   │   │   │   ├── extended_data.kml
│   │   │   │   ├── gx_track.kml
│   │   │   │   ├── invalid_coordinates.kml
│   │   │   │   ├── large_track.kml
│   │   │   │   ├── linestring_track.kml
│   │   │   │   ├── multigeometry.kml
│   │   │   │   ├── nested_folders.kml
│   │   │   │   ├── points_with_timestamps.kml
│   │   │   │   ├── points_with_timestamps.kmz
│   │   │   │   └── timespan.kml
│   │   │   ├── overland/
│   │   │   │   └── geodata.json
│   │   │   ├── owntracks/
│   │   │   │   ├── 2023-02_old.rec
│   │   │   │   └── 2024-03.rec
│   │   │   ├── points/
│   │   │   │   └── geojson_example.json
│   │   │   └── watched/
│   │   │       ├── invalid_user@domain.com/
│   │   │       │   └── location-history.json
│   │   │       └── user@domain.com/
│   │   │           ├── 2023_January.json
│   │   │           ├── Records.json
│   │   │           ├── export_same_points.json
│   │   │           ├── gpx_track_single_segment.gpx
│   │   │           ├── location-history.json
│   │   │           └── owntracks.rec
│   │   └── users/
│   │       └── welcome
│   ├── helpers/
│   │   ├── application_helper_spec.rb
│   │   ├── insights_helper_spec.rb
│   │   └── stats_helper_spec.rb
│   ├── integration/
│   │   └── family_privacy_spec.rb
│   ├── jobs/
│   │   ├── app_version_checking_job_spec.rb
│   │   ├── application_job_spec.rb
│   │   ├── area_visits_calculating_job_spec.rb
│   │   ├── area_visits_calculation_scheduling_job_spec.rb
│   │   ├── bulk_stats_calculating_job_spec.rb
│   │   ├── bulk_visits_suggesting_job_spec.rb
│   │   ├── cache/
│   │   │   └── preheating_job_spec.rb
│   │   ├── concerns/
│   │   │   └── user_timezone_spec.rb
│   │   ├── data_migrations/
│   │   │   ├── backfill_motion_data_job_spec.rb
│   │   │   ├── backfill_onboarding_completed_job_spec.rb
│   │   │   ├── fix_route_opacity_job_spec.rb
│   │   │   ├── migrate_places_lonlat_job_spec.rb
│   │   │   ├── migrate_points_latlon_job_spec.rb
│   │   │   ├── set_points_country_ids_job_spec.rb
│   │   │   └── start_settings_points_country_ids_job_spec.rb
│   │   ├── enqueue_background_job_spec.rb
│   │   ├── export_job_spec.rb
│   │   ├── families/
│   │   │   └── expire_location_requests_job_spec.rb
│   │   ├── family/
│   │   │   └── invitations/
│   │   │       └── sending_job_spec.rb
│   │   ├── import/
│   │   │   ├── immich_geodata_job_spec.rb
│   │   │   ├── process_job_spec.rb
│   │   │   └── watcher_job_spec.rb
│   │   ├── imports/
│   │   │   └── destroy_job_spec.rb
│   │   ├── lite/
│   │   │   └── archival_warning_job_spec.rb
│   │   ├── places/
│   │   │   ├── bulk_name_fetching_job_spec.rb
│   │   │   └── name_fetching_job_spec.rb
│   │   ├── points/
│   │   │   ├── create_job_spec.rb
│   │   │   ├── nightly_reverse_geocoding_job_spec.rb
│   │   │   └── raw_data/
│   │   │       ├── archive_job_spec.rb
│   │   │       └── re_archive_month_job_spec.rb
│   │   ├── reverse_geocoding_job_spec.rb
│   │   ├── stale_jobs_recovery_job_spec.rb
│   │   ├── stats/
│   │   │   └── calculating_job_spec.rb
│   │   ├── tracks/
│   │   │   ├── daily_generation_job_spec.rb
│   │   │   ├── deduplication_job_spec.rb
│   │   │   ├── parallel_generator_job_spec.rb
│   │   │   ├── realtime_generation_job_spec.rb
│   │   │   ├── recalculate_job_spec.rb
│   │   │   └── transportation_mode_recalculation_job_spec.rb
│   │   ├── trips/
│   │   │   └── calculate_countries_job_spec.rb
│   │   ├── users/
│   │   │   ├── destroy_job_spec.rb
│   │   │   ├── digests/
│   │   │   │   ├── calculating_job_spec.rb
│   │   │   │   ├── email_sending_job_spec.rb
│   │   │   │   └── year_end_scheduling_job_spec.rb
│   │   │   ├── export_data_job_spec.rb
│   │   │   ├── import_data_job_spec.rb
│   │   │   ├── mailer_sending_job_spec.rb
│   │   │   ├── recalculate_data_job_spec.rb
│   │   │   └── trial_webhook_job_spec.rb
│   │   └── visit_suggesting_job_spec.rb
│   ├── lib/
│   │   ├── dawarich_settings_spec.rb
│   │   └── json_stream_handler_spec.rb
│   ├── mailers/
│   │   ├── family_mailer_spec.rb
│   │   ├── previews/
│   │   │   ├── users/
│   │   │   │   └── digests_mailer_preview.rb
│   │   │   └── users_mailer_preview.rb
│   │   └── users_mailer_spec.rb
│   ├── models/
│   │   ├── area_spec.rb
│   │   ├── concerns/
│   │   │   ├── archivable_spec.rb
│   │   │   ├── plan_scopable_spec.rb
│   │   │   ├── point_validation_spec.rb
│   │   │   ├── soft_deletable_spec.rb
│   │   │   ├── taggable_spec.rb
│   │   │   └── user_family_spec.rb
│   │   ├── country_spec.rb
│   │   ├── export_spec.rb
│   │   ├── family/
│   │   │   ├── invitation_spec.rb
│   │   │   ├── location_request_spec.rb
│   │   │   └── membership_spec.rb
│   │   ├── family_spec.rb
│   │   ├── import_spec.rb
│   │   ├── notification_spec.rb
│   │   ├── place_spec.rb
│   │   ├── place_visit_spec.rb
│   │   ├── point_spec.rb
│   │   ├── points/
│   │   │   └── raw_data_archive_spec.rb
│   │   ├── stat_spec.rb
│   │   ├── tag_spec.rb
│   │   ├── tagging_spec.rb
│   │   ├── track_segment_spec.rb
│   │   ├── track_spec.rb
│   │   ├── trip_spec.rb
│   │   ├── user_family_spec.rb
│   │   ├── user_spec.rb
│   │   ├── users/
│   │   │   └── digest_spec.rb
│   │   └── visit_spec.rb
│   ├── policies/
│   │   ├── family/
│   │   │   ├── invitation_policy_spec.rb
│   │   │   └── membership_policy_spec.rb
│   │   ├── import_policy_spec.rb
│   │   └── tag_policy_spec.rb
│   ├── queries/
│   │   ├── stats/
│   │   │   ├── daily_distance_query_spec.rb
│   │   │   └── time_of_day_query_spec.rb
│   │   └── stats_query_spec.rb
│   ├── rails_helper.rb
│   ├── requests/
│   │   ├── api/
│   │   │   └── v1/
│   │   │       ├── areas_spec.rb
│   │   │       ├── countries/
│   │   │       │   ├── borders_spec.rb
│   │   │       │   └── visited_cities_spec.rb
│   │   │       ├── digests_spec.rb
│   │   │       ├── families/
│   │   │       │   └── locations_spec.rb
│   │   │       ├── health_spec.rb
│   │   │       ├── imports_spec.rb
│   │   │       ├── insights_spec.rb
│   │   │       ├── locations_spec.rb
│   │   │       ├── maps/
│   │   │       │   └── hexagons_spec.rb
│   │   │       ├── overland/
│   │   │       │   └── batches_spec.rb
│   │   │       ├── owntracks/
│   │   │       │   └── points_spec.rb
│   │   │       ├── photos_spec.rb
│   │   │       ├── places_spec.rb
│   │   │       ├── plan_spec.rb
│   │   │       ├── points/
│   │   │       │   └── tracked_months_spec.rb
│   │   │       ├── points_spec.rb
│   │   │       ├── rate_limiting_spec.rb
│   │   │       ├── settings_spec.rb
│   │   │       ├── stats_spec.rb
│   │   │       ├── subscriptions_spec.rb
│   │   │       ├── tags_spec.rb
│   │   │       ├── timeline_spec.rb
│   │   │       ├── tracks/
│   │   │       │   └── points_spec.rb
│   │   │       ├── tracks_spec.rb
│   │   │       ├── users_spec.rb
│   │   │       ├── visits/
│   │   │       │   └── possible_places_spec.rb
│   │   │       └── visits_spec.rb
│   │   ├── areas_spec.rb
│   │   ├── authentication_spec.rb
│   │   ├── exports_spec.rb
│   │   ├── families_spec.rb
│   │   ├── family/
│   │   │   ├── invitations_spec.rb
│   │   │   ├── location_requests_spec.rb
│   │   │   ├── location_sharing_spec.rb
│   │   │   └── memberships_spec.rb
│   │   ├── family_workflows_spec.rb
│   │   ├── home_spec.rb
│   │   ├── imports_spec.rb
│   │   ├── insights_spec.rb
│   │   ├── map/
│   │   │   └── timeline_feeds_spec.rb
│   │   ├── map_spec.rb
│   │   ├── notifications_spec.rb
│   │   ├── places_spec.rb
│   │   ├── points_spec.rb
│   │   ├── settings/
│   │   │   ├── background_jobs_spec.rb
│   │   │   ├── general_spec.rb
│   │   │   ├── integrations_spec.rb
│   │   │   ├── maps_spec.rb
│   │   │   ├── onboarding_spec.rb
│   │   │   └── users_spec.rb
│   │   ├── settings_spec.rb
│   │   ├── shared/
│   │   │   ├── digests_spec.rb
│   │   │   └── stats_spec.rb
│   │   ├── sidekiq_spec.rb
│   │   ├── stats_spec.rb
│   │   ├── tags_spec.rb
│   │   ├── timezone_spec.rb
│   │   ├── trips_spec.rb
│   │   ├── users/
│   │   │   ├── digests_spec.rb
│   │   │   ├── omniauth_callbacks_spec.rb
│   │   │   ├── registrations_spec.rb
│   │   │   └── sessions_spec.rb
│   │   ├── users_spec.rb
│   │   └── visits_spec.rb
│   ├── serializers/
│   │   ├── api/
│   │   │   ├── digest_list_serializer_spec.rb
│   │   │   ├── photo_serializer_spec.rb
│   │   │   ├── place_serializer_spec.rb
│   │   │   ├── point_serializer_spec.rb
│   │   │   ├── slim_point_serializer_spec.rb
│   │   │   ├── user_serializer_spec.rb
│   │   │   └── visit_serializer_spec.rb
│   │   ├── export_serializer_spec.rb
│   │   ├── exports/
│   │   │   ├── point_geojson_serializer_spec.rb
│   │   │   └── point_gpx_serializer_spec.rb
│   │   ├── point_serializer_spec.rb
│   │   ├── points/
│   │   │   ├── geojson_serializer_spec.rb
│   │   │   └── gpx_serializer_spec.rb
│   │   ├── stats_serializer_spec.rb
│   │   ├── tag_serializer_spec.rb
│   │   ├── track_serializer_spec.rb
│   │   ├── tracks/
│   │   │   └── geojson_serializer_spec.rb
│   │   └── tracks_serializer_spec.rb
│   ├── services/
│   │   ├── areas/
│   │   │   └── visits/
│   │   │       └── create_spec.rb
│   │   ├── cache/
│   │   │   ├── clean_spec.rb
│   │   │   └── invalidate_user_caches_spec.rb
│   │   ├── check_app_version_spec.rb
│   │   ├── concerns/
│   │   │   └── ssl_configurable_spec.rb
│   │   ├── countries/
│   │   │   └── iso_code_mapper_spec.rb
│   │   ├── countries_and_cities_spec.rb
│   │   ├── exports/
│   │   │   └── create_spec.rb
│   │   ├── families/
│   │   │   ├── accept_invitation_spec.rb
│   │   │   ├── create_location_request_spec.rb
│   │   │   ├── create_spec.rb
│   │   │   ├── invite_spec.rb
│   │   │   ├── locations_spec.rb
│   │   │   ├── memberships/
│   │   │   │   └── destroy_spec.rb
│   │   │   └── update_location_sharing_spec.rb
│   │   ├── geojson/
│   │   │   ├── importer_spec.rb
│   │   │   └── params_spec.rb
│   │   ├── google_maps/
│   │   │   ├── phone_takeout_importer_spec.rb
│   │   │   ├── records_importer_spec.rb
│   │   │   ├── records_storage_importer_spec.rb
│   │   │   └── semantic_history_importer_spec.rb
│   │   ├── gpx/
│   │   │   └── track_importer_spec.rb
│   │   ├── immich/
│   │   │   ├── connection_tester_spec.rb
│   │   │   ├── import_geodata_spec.rb
│   │   │   ├── request_photos_spec.rb
│   │   │   ├── response_analyzer_spec.rb
│   │   │   └── response_validator_spec.rb
│   │   ├── imports/
│   │   │   ├── create_spec.rb
│   │   │   ├── destroy_spec.rb
│   │   │   ├── secure_file_downloader_spec.rb
│   │   │   ├── source_detector_spec.rb
│   │   │   └── watcher_spec.rb
│   │   ├── insights/
│   │   │   ├── activity_heatmap_calculator_spec.rb
│   │   │   ├── travel_insight_generator_spec.rb
│   │   │   ├── travel_patterns_loader_spec.rb
│   │   │   ├── year_comparison_calculator_spec.rb
│   │   │   └── year_totals_calculator_spec.rb
│   │   ├── jobs/
│   │   │   └── create_spec.rb
│   │   ├── kml/
│   │   │   └── importer_spec.rb
│   │   ├── location_search/
│   │   │   ├── geocoding_service_spec.rb
│   │   │   ├── point_finder_spec.rb
│   │   │   ├── result_aggregator_spec.rb
│   │   │   └── spatial_matcher_spec.rb
│   │   ├── maps/
│   │   │   ├── bounds_calculator_spec.rb
│   │   │   ├── hexagon_center_manager_spec.rb
│   │   │   ├── hexagon_polygon_generator_spec.rb
│   │   │   └── hexagon_request_handler_spec.rb
│   │   ├── metrics/
│   │   │   └── archives/
│   │   │       ├── compression_ratio_spec.rb
│   │   │       ├── count_mismatch_spec.rb
│   │   │       ├── operation_spec.rb
│   │   │       ├── points_archived_spec.rb
│   │   │       ├── size_spec.rb
│   │   │       └── verification_spec.rb
│   │   ├── notifications/
│   │   │   └── create_spec.rb
│   │   ├── overland/
│   │   │   ├── params_spec.rb
│   │   │   └── points_creator_spec.rb
│   │   ├── own_tracks/
│   │   │   ├── importer_spec.rb
│   │   │   ├── params_spec.rb
│   │   │   └── point_creator_spec.rb
│   │   ├── photoprism/
│   │   │   ├── cache_preview_token_spec.rb
│   │   │   ├── connection_tester_spec.rb
│   │   │   ├── import_geodata_spec.rb
│   │   │   ├── request_photos_spec.rb
│   │   │   └── response_validator_spec.rb
│   │   ├── photos/
│   │   │   ├── cache_cleaner_spec.rb
│   │   │   ├── importer_spec.rb
│   │   │   ├── search_spec.rb
│   │   │   └── thumbnail_spec.rb
│   │   ├── places/
│   │   │   └── name_fetcher_spec.rb
│   │   ├── points/
│   │   │   ├── create_spec.rb
│   │   │   ├── live_broadcaster_spec.rb
│   │   │   ├── motion_data_extractor_spec.rb
│   │   │   ├── params_spec.rb
│   │   │   ├── raw_data/
│   │   │   │   ├── archiver_spec.rb
│   │   │   │   ├── chunk_compressor_spec.rb
│   │   │   │   ├── clearer_spec.rb
│   │   │   │   ├── encryption_spec.rb
│   │   │   │   ├── restorer_spec.rb
│   │   │   │   └── verifier_spec.rb
│   │   │   └── raw_data_lonlat_extractor_spec.rb
│   │   ├── points_limit_exceeded_spec.rb
│   │   ├── reverse_geocoding/
│   │   │   ├── places/
│   │   │   │   └── fetch_data_spec.rb
│   │   │   └── points/
│   │   │       └── fetch_data_spec.rb
│   │   ├── settings/
│   │   │   └── update_spec.rb
│   │   ├── stats/
│   │   │   ├── bulk_calculator_spec.rb
│   │   │   ├── calculate_month_spec.rb
│   │   │   └── hexagon_calculator_spec.rb
│   │   ├── subscription/
│   │   │   └── encode_jwt_token_spec.rb
│   │   ├── tasks/
│   │   │   └── imports/
│   │   │       └── google_records_spec.rb
│   │   ├── timeline/
│   │   │   └── day_assembler_spec.rb
│   │   ├── tracks/
│   │   │   ├── boundary_detector_spec.rb
│   │   │   ├── build_path_spec.rb
│   │   │   ├── deduplicator_spec.rb
│   │   │   ├── incremental_generator_spec.rb
│   │   │   ├── index_query_spec.rb
│   │   │   ├── merger_spec.rb
│   │   │   ├── parallel_generator_spec.rb
│   │   │   ├── realtime_debouncer_spec.rb
│   │   │   ├── segmentation_spec.rb
│   │   │   ├── session_manager_spec.rb
│   │   │   ├── time_chunker_spec.rb
│   │   │   ├── track_builder_spec.rb
│   │   │   └── transportation_recalculation_status_spec.rb
│   │   ├── transportation_modes/
│   │   │   ├── detector_spec.rb
│   │   │   ├── mode_classifier_spec.rb
│   │   │   ├── movement_analyzer_spec.rb
│   │   │   └── source_data_extractor_spec.rb
│   │   ├── trips/
│   │   │   └── photos_spec.rb
│   │   ├── users/
│   │   │   ├── destroy_spec.rb
│   │   │   ├── digests/
│   │   │   │   ├── activity_breakdown_calculator_spec.rb
│   │   │   │   ├── calculate_year_spec.rb
│   │   │   │   ├── first_time_visits_calculator_spec.rb
│   │   │   │   └── year_over_year_calculator_spec.rb
│   │   │   ├── export_data/
│   │   │   │   ├── areas_spec.rb
│   │   │   │   ├── digests_spec.rb
│   │   │   │   ├── exports_spec.rb
│   │   │   │   ├── imports_spec.rb
│   │   │   │   ├── notifications_spec.rb
│   │   │   │   ├── places_spec.rb
│   │   │   │   ├── points_spec.rb
│   │   │   │   ├── stats_spec.rb
│   │   │   │   ├── tracks_spec.rb
│   │   │   │   ├── trips_spec.rb
│   │   │   │   └── visits_spec.rb
│   │   │   ├── export_data_spec.rb
│   │   │   ├── export_import_integration_spec.rb
│   │   │   ├── import_data/
│   │   │   │   ├── areas_spec.rb
│   │   │   │   ├── digests_spec.rb
│   │   │   │   ├── exports_spec.rb
│   │   │   │   ├── imports_spec.rb
│   │   │   │   ├── notifications_spec.rb
│   │   │   │   ├── places_spec.rb
│   │   │   │   ├── places_streaming_spec.rb
│   │   │   │   ├── points_spec.rb
│   │   │   │   ├── raw_data_archives_spec.rb
│   │   │   │   ├── settings_spec.rb
│   │   │   │   ├── stats_spec.rb
│   │   │   │   ├── taggings_spec.rb
│   │   │   │   ├── tags_spec.rb
│   │   │   │   ├── tracks_spec.rb
│   │   │   │   ├── trips_spec.rb
│   │   │   │   ├── v1_handler_spec.rb
│   │   │   │   ├── v2_handler_spec.rb
│   │   │   │   └── visits_spec.rb
│   │   │   ├── import_data_spec.rb
│   │   │   ├── safe_settings_spec.rb
│   │   │   └── transportation_thresholds_updater_spec.rb
│   │   └── visits/
│   │       ├── bulk_update_spec.rb
│   │       ├── create_spec.rb
│   │       ├── creator_spec.rb
│   │       ├── detector_spec.rb
│   │       ├── find_in_time_spec.rb
│   │       ├── find_within_bounding_box_spec.rb
│   │       ├── finder_spec.rb
│   │       ├── group_spec.rb
│   │       ├── merge_service_spec.rb
│   │       ├── merger_spec.rb
│   │       ├── names/
│   │       │   ├── builder_spec.rb
│   │       │   └── suggester_spec.rb
│   │       ├── place_finder_spec.rb
│   │       ├── smart_detect_spec.rb
│   │       ├── suggest_spec.rb
│   │       └── time_chunks_spec.rb
│   ├── spec_helper.rb
│   ├── support/
│   │   ├── capybara.rb
│   │   ├── devise.rb
│   │   ├── geocoder_stubs.rb
│   │   ├── github_api_stubs.rb
│   │   ├── omniauth.rb
│   │   ├── pundit_matchers.rb
│   │   ├── redis.rb
│   │   ├── swagger_response_example.rb
│   │   └── turbo_stream_helpers.rb
│   ├── swagger/
│   │   └── api/
│   │       └── v1/
│   │           ├── areas_controller_spec.rb
│   │           ├── countries/
│   │           │   ├── borders_controller_spec.rb
│   │           │   └── visited_cities_spec.rb
│   │           ├── digests_controller_spec.rb
│   │           ├── families/
│   │           │   └── locations_controller_spec.rb
│   │           ├── health_controller_spec.rb
│   │           ├── imports_controller_spec.rb
│   │           ├── insights_controller_spec.rb
│   │           ├── locations_controller_spec.rb
│   │           ├── maps/
│   │           │   └── hexagons_controller_spec.rb
│   │           ├── overland/
│   │           │   └── batches_controller_spec.rb
│   │           ├── owntracks/
│   │           │   └── points_controller_spec.rb
│   │           ├── photos_controller_spec.rb
│   │           ├── places_controller_spec.rb
│   │           ├── points/
│   │           │   └── tracked_months_controller_spec.rb
│   │           ├── points_controller_spec.rb
│   │           ├── settings_controller_spec.rb
│   │           ├── stats_controller_spec.rb
│   │           ├── subscriptions_controller_spec.rb
│   │           ├── tags_controller_spec.rb
│   │           ├── timeline_controller_spec.rb
│   │           ├── tracks/
│   │           │   └── points_controller_spec.rb
│   │           ├── tracks_controller_spec.rb
│   │           ├── users_controller_spec.rb
│   │           └── visits_controller_spec.rb
│   ├── swagger_helper.rb
│   └── tasks/
│       ├── import_spec.rb
│       └── points_raw_data_reset_all_spec.rb
├── storage/
│   └── .keep
├── swagger/
│   └── v1/
│       └── swagger.yaml
├── tmp/
│   └── .keep
└── vendor/
    ├── .keep
    └── javascript/
        ├── .keep
        ├── @rails--ujs.js
        ├── emoji-mart.js
        ├── leaflet-draw.js
        ├── leaflet-providers.js
        ├── leaflet.control.layers.tree.js
        ├── leaflet.heat.js
        ├── leaflet.js
        └── maplibre-gl.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .app_version
================================================
1.3.4


================================================
FILE: .circleci/config.yml
================================================
version: 2.1

orbs:
  ruby: circleci/ruby@2.1.4
  browser-tools: circleci/browser-tools@1.4.8

jobs:
  test:
    docker:
      - image: cimg/ruby:3.4.6-browsers
        environment:
          RAILS_ENV: test
          CI: true
          DATABASE_HOST: localhost
          DATABASE_NAME: dawarich_test
          DATABASE_USERNAME: postgres
          DATABASE_PASSWORD: mysecretpassword
          DATABASE_PORT: 5432
      - image: cimg/postgres:13.3-postgis
        environment:
          POSTGRES_USER: postgres
          POSTGRES_DB: dawarich_test
          POSTGRES_PASSWORD: mysecretpassword
      - image: redis:7.0
      - image: selenium/standalone-chrome:latest
        name: chrome
        environment:
          START_XVFB: 'false'
          JAVA_OPTS: -Dwebdriver.chrome.whitelistedIps=

    steps:
      - checkout
      - browser-tools/install-chrome
      - browser-tools/install-chromedriver
      - run:
          name: Install Bundler
          command: gem install bundler
      - run:
          name: Bundle Install
          command: bundle install --jobs=4 --retry=3
      - run:
          name: Wait for Selenium Chrome
          command: |
            dockerize -wait tcp://chrome:4444 -timeout 1m
      - run:
          name: Database Setup
          command: |
            bundle exec rails db:create RAILS_ENV=test
            bundle exec rails db:schema:load RAILS_ENV=test
            # Create the queue database manually if it doesn't exist
            PGPASSWORD=mysecretpassword createdb -h localhost -U postgres dawarich_test_queue || true
      - run:
          name: Run RSpec tests
          command: bundle exec rspec
      - store_artifacts:
          path: coverage
      - store_artifacts:
          path: tmp/capybara

workflows:
  rspec:
    jobs:
      - test


================================================
FILE: .devcontainer/Dockerfile
================================================
# Base-Image for Ruby and Node.js
FROM ruby:3.4.6-alpine

ENV APP_PATH=/var/app
ENV BUNDLE_VERSION=2.5.21
ENV BUNDLE_PATH=/usr/local/bundle/gems
ENV TMP_PATH=/tmp/
ENV RAILS_LOG_TO_STDOUT=true
ENV RAILS_PORT=3000

# Install dependencies for application
RUN apk -U add --no-cache \
    build-base \
    git \
    postgresql-dev \
    postgresql-client \
    libxml2-dev \
    libxslt-dev \
    nodejs \
    yarn \
    imagemagick \
    tzdata \
    less \
    yaml-dev \
    # gcompat for nokogiri on mac m1
    gcompat \
    && rm -rf /var/cache/apk/* \
    && mkdir -p $APP_PATH

RUN gem update --system 3.6.2 && gem install bundler --version "$BUNDLE_VERSION" \
    && rm -rf $GEM_HOME/cache/*

# FIXME It would be a good idea to use a other user than root, but this lead to permission error on export and maybe more yet.
# RUN adduser -D -h ${APP_PATH} vscode
USER root

# Navigate to app directory
WORKDIR $APP_PATH

EXPOSE $RAILS_PORT



================================================
FILE: .devcontainer/devcontainer.json
================================================
{
  "name": "Ruby and Node DevContainer",
  "dockerComposeFile": ["docker-compose.yml"],
  "service": "dawarich_dev",
  "settings": {
    "terminal.integrated.defaultProfile.linux": "bash"
  },
  "extensions": [
    "rebornix.ruby", // Ruby-Support
    "esbenp.prettier-vscode", // Prettier for JS-Formating
    "dbaeumer.vscode-eslint" // ESLint for JavaScript
  ],
  "postCreateCommand": "yarn install && bundle config set --local path 'vendor/bundle' && bundle install --jobs 20 --retry 5",
  "forwardPorts": [3000], // Redirect to Rails-App-Server
  "remoteUser": "root",
  "workspaceFolder": "/var/app"
}


================================================
FILE: .devcontainer/docker-compose.yml
================================================
networks:
  dawarich:
services:
  dawarich_dev:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: dawarich_dev
    volumes:
      - dawarich_public:/var/app/public
      - dawarich_watched:/var/app/tmp/imports/watched
      - dawarich_storage:/var/app/storage
    networks:
      - dawarich
    ports:
      - 3000:3000
      - 9394:9394
    stdin_open: true
    tty: true
    environment:
      RAILS_ENV: development
      REDIS_URL: redis://dawarich_redis:6379
      DATABASE_HOST: dawarich_db
      DATABASE_USERNAME: postgres
      DATABASE_PASSWORD: password
      DATABASE_NAME: dawarich_development
      APPLICATION_HOSTS: localhost
      TIME_ZONE: Europe/London
      APPLICATION_PROTOCOL: http
      PROMETHEUS_EXPORTER_ENABLED: false
      PROMETHEUS_EXPORTER_HOST: 0.0.0.0
      PROMETHEUS_EXPORTER_PORT: 9394
  dawarich_redis:
    image: redis:7.4-alpine
    container_name: dawarich_redis
    command: redis-server
    networks:
      - dawarich
    volumes:
      - dawarich_shared:/data
    restart: always
    healthcheck:
      test: [ "CMD", "redis-cli", "--raw", "incr", "ping" ]
      interval: 10s
      retries: 5
      start_period: 30s
      timeout: 10s
  dawarich_db:
    image: postgis/postgis:17-3.5-alpine
    container_name: dawarich_db
    volumes:
      - dawarich_db_data:/var/lib/postgresql/data
      - dawarich_shared:/var/shared
    networks:
      - dawarich
    restart: always
    healthcheck:
      test: [ "CMD-SHELL", "pg_isready -U postgres -d dawarich_development" ]
      interval: 10s
      retries: 5
      start_period: 30s
      timeout: 10s
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
volumes:
  dawarich_db_data:
  dawarich_shared:
  dawarich_public:
  dawarich_watched:
  dawarich_storage:


================================================
FILE: .dockerignore
================================================
/log
/tmp

# We need directories for import and export files, but not the files themselves.
/public/exports/*
!/public/exports/.keep
/public/imports/*
!/public/imports/.keep

.git/
.github/
docs/
.circleci/
.devcontainer/
screenshots/
.ruby-lsp/


================================================
FILE: .gitattributes
================================================
# See https://git-scm.com/docs/gitattributes for more about git attribute files.

# Mark the database schema as having been generated.
db/schema.rb linguist-generated

# Mark any vendored files as having been vendored.
vendor/* linguist-vendored


================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms

github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: freika # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: freika
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''

---

**BEFORE OPENING AN ISSUE, MAKE SURE YOU READ THIS: https://github.com/Freika/dawarich/issues/1382**

**OS & Hardware**
Provide your software and hardware specs

**Version**
Provide the version of Dawarich you're experiencing the problem on.

**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error

**Expected behavior**
A clear and concise description of what you expected to happen.

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Logs**
If applicable, add logs from the `dawarich_app` container to help explain your problem.

**Additional context**
Add any other context about the problem here.


================================================
FILE: .github/dependabot.yml
================================================
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates

version: 2
updates:
  - package-ecosystem: 'bundler'
    directory: '/'
    schedule:
      interval: 'weekly'


================================================
FILE: .github/workflows/attach_compose.yml
================================================
name: Attach docker-compose.yml to release

on:
  release:
    types: [published]

permissions:
  contents: write

jobs:
  attach-compose:
    if: ${{ !github.event.release.prerelease }}
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Upload docker-compose.yml to release
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: gh release upload "${{ github.event.release.tag_name }}" docker/docker-compose.yml


================================================
FILE: .github/workflows/biome.yml
================================================
name: biome
on:
  push:
  pull_request:
jobs:
  quality:
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - name: Checkout
        uses: actions/checkout@v6
        with:
          persist-credentials: false
          fetch-depth: 0
      - name: Determine base ref
        id: base
        env:
          BASE_REF: ${{ github.base_ref }}
          EVENT_NAME: ${{ github.event_name }}
        run: |
          if [ "$EVENT_NAME" = "pull_request" ]; then
            CURRENT=$(git rev-parse --abbrev-ref HEAD)
            if [ "$BASE_REF" != "$CURRENT" ]; then
              git fetch origin "$BASE_REF":"$BASE_REF"
            fi
            echo "ref=$BASE_REF" >> "$GITHUB_OUTPUT"
          else
            echo "ref=HEAD~1" >> "$GITHUB_OUTPUT"
          fi
      - name: Setup Biome
        uses: biomejs/setup-biome@v2
        with:
          version: 2.3.11
      - name: Run Biome
        env:
          BASE: ${{ steps.base.outputs.ref }}
        run: biome ci . --reporter=github --changed --since="$BASE" --no-errors-on-unmatched


================================================
FILE: .github/workflows/build_and_push.yml
================================================
name: Docker image build and push

on:
  workflow_dispatch:
    inputs:
      branch:
        description: "The branch to build the Docker image from"
        required: false
        default: "master"
  release:
    types: [created]

permissions: {}

jobs:
  prepare:
    runs-on: ubuntu-22.04
    outputs:
      version: ${{ steps.meta.outputs.version }}
      is_prerelease: ${{ steps.meta.outputs.is_prerelease }}
      platforms: ${{ steps.meta.outputs.platforms }}
      matrix: ${{ steps.meta.outputs.matrix }}
    steps:
      - name: Compute version and platforms
        id: meta
        run: |
          if [[ $GITHUB_REF == refs/tags/* ]]; then
            VERSION=${GITHUB_REF#refs/tags/}
          else
            VERSION=$GITHUB_REF_NAME
          fi
          if [ -z "$VERSION" ]; then
            VERSION="rc"
          fi

          IS_PRERELEASE="${{ github.event.release.prerelease }}"
          PLATFORMS="linux/amd64,linux/arm64,linux/arm/v7"
          MATRIX='{"include":[{"platform":"linux/amd64","runner":"ubuntu-22.04"},{"platform":"linux/arm64","runner":"ubuntu-22.04-arm"},{"platform":"linux/arm/v7","runner":"ubuntu-22.04-arm"}]}'

          echo "version=${VERSION}" >> $GITHUB_OUTPUT
          echo "is_prerelease=${IS_PRERELEASE}" >> $GITHUB_OUTPUT
          echo "platforms=${PLATFORMS}" >> $GITHUB_OUTPUT
          echo "matrix=${MATRIX}" >> $GITHUB_OUTPUT

  build-and-push-docker:
    permissions:
      contents: read
    needs: prepare
    strategy:
      fail-fast: false
      matrix: ${{ fromJSON(needs.prepare.outputs.matrix) }}
    runs-on: ${{ matrix.runner }}
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          ref: ${{ github.event.inputs.branch || github.ref_name }}

      # QEMU only needed for arm/v7 (armv7 emulated on aarch64 runner)
      - name: Set up QEMU
        if: matrix.platform == 'linux/arm/v7'
        uses: docker/setup-qemu-action@v3
        with:
          platforms: linux/arm/v7

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Cache Docker layers
        uses: actions/cache@v4
        with:
          path: /tmp/.buildx-cache
          key: ${{ runner.os }}-${{ matrix.platform }}-buildx-${{ github.sha }}
          restore-keys: |
            ${{ runner.os }}-${{ matrix.platform }}-buildx-

      - name: Install dependencies
        run: npm install

      - name: Docker meta
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ secrets.DOCKERHUB_USERNAME }}/dawarich

      - name: Login to Docker Hub
        uses: docker/login-action@v3.1.0
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Prepare platform pair
        id: platform
        run: |
          PAIR=$(echo "${{ matrix.platform }}" | tr '/' '-')
          echo "pair=${PAIR}" >> "$GITHUB_OUTPUT"

      - name: Build and push by digest
        id: build
        uses: docker/build-push-action@v5
        with:
          context: .
          file: ./docker/Dockerfile
          platforms: ${{ matrix.platform }}
          outputs: type=image,name=${{ secrets.DOCKERHUB_USERNAME }}/dawarich,push-by-digest=true,name-canonical=true,push=true
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=local,src=/tmp/.buildx-cache
          cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max

      - name: Rotate cache
        run: |
          rm -rf /tmp/.buildx-cache
          mv /tmp/.buildx-cache-new /tmp/.buildx-cache

      - name: Export digest
        run: |
          mkdir -p "${{ runner.temp }}/digests"
          DIGEST="${{ steps.build.outputs.digest }}"
          touch "${{ runner.temp }}/digests/${DIGEST#sha256:}"

      - name: Upload digest
        uses: actions/upload-artifact@v4
        with:
          name: digest-${{ steps.platform.outputs.pair }}
          path: ${{ runner.temp }}/digests/*
          if-no-files-found: error
          retention-days: 1

  merge:
    permissions:
      contents: read
    needs: [prepare, build-and-push-docker]
    runs-on: ubuntu-22.04
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          ref: ${{ github.event.inputs.branch || github.ref_name }}

      - name: Login to Docker Hub
        uses: docker/login-action@v3.1.0
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Download all digests
        uses: actions/download-artifact@v4
        with:
          path: ${{ runner.temp }}/digests
          pattern: digest-*
          merge-multiple: true

      - name: Build Docker tags
        id: docker_tags
        run: |
          IMAGE="${{ secrets.DOCKERHUB_USERNAME }}/dawarich"
          VERSION="${{ needs.prepare.outputs.version }}"
          IS_PRERELEASE="${{ needs.prepare.outputs.is_prerelease }}"

          TAGS="${IMAGE}:${VERSION}"

          if [ "$IS_PRERELEASE" = "true" ]; then
            TAGS="${TAGS},${IMAGE}:rc"
          else
            TAGS="${TAGS},${IMAGE}:latest"
          fi

          echo "tags=${TAGS}" >> $GITHUB_OUTPUT

      - name: Create manifest list and push
        working-directory: ${{ runner.temp }}/digests
        run: |
          TAGS="${{ steps.docker_tags.outputs.tags }}"

          TAG_ARGS=""
          IFS=',' read -ra TAG_LIST <<< "$TAGS"
          for TAG in "${TAG_LIST[@]}"; do
            TAG_ARGS="${TAG_ARGS} -t ${TAG}"
          done

          SOURCES=$(printf "${{ secrets.DOCKERHUB_USERNAME }}/dawarich@sha256:%s " *)

          docker buildx imagetools create ${TAG_ARGS} ${SOURCES}

      - name: Inspect final image
        run: |
          TAGS="${{ steps.docker_tags.outputs.tags }}"
          FIRST_TAG="${TAGS%%,*}"
          docker buildx imagetools inspect "${FIRST_TAG}"


================================================
FILE: .github/workflows/ci.yml
================================================
name: CI
# Not functional at the moment

on:
  pull_request:
  push:
    branches: [main]

permissions:
  contents: read

jobs:
  test:
    runs-on: ubuntu-latest

    services:
      postgres:
        image: postgres
        env:
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: postgres
        ports:
          - 5432:5432
        options: --health-cmd="pg_isready" --health-interval=10s --health-timeout=5s --health-retries=3
      redis:
        image: redis
        ports:
          - 6379:6379

    steps:
      - name: Install packages
        run: sudo apt-get update && sudo apt-get install --no-install-recommends -y google-chrome-stable curl libjemalloc2 libvips postgresql-client libpq-dev

      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: '3.4.6'
          bundler-cache: true

      - name: Set up Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '16'  # Use the appropriate Node.js version

      - name: Install Node.js dependencies
        run: npm install

      - name: Install Ruby dependencies
        run: bundle install

      - name: Run bundler audit
        run: |
          gem install bundler-audit
          bundle audit --update

      - name: Setup database
        env:
          RAILS_ENV: test
          DATABASE_URL: postgres://postgres:postgres@localhost:5432
          REDIS_URL: redis://localhost:6379/1
        run: bin/rails db:setup

      - name: Run main tests (excluding system tests)
        env:
          RAILS_ENV: test
          DATABASE_URL: postgres://postgres:postgres@localhost:5432
          REDIS_URL: redis://localhost:6379/1
        run: |
          bundle exec rspec --exclude-pattern "spec/system/**/*_spec.rb" || (cat log/test.log && exit 1)

      - name: Run system tests
        env:
          RAILS_ENV: test
          DATABASE_URL: postgres://postgres:postgres@localhost:5432
          REDIS_URL: redis://localhost:6379/1
        run: |
          bundle exec rspec spec/system/ || (cat log/test.log && exit 1)

      - name: Keep screenshots from failed system tests
        uses: actions/upload-artifact@v4
        if: failure()
        with:
          name: screenshots
          path: ${{ github.workspace }}/tmp/capybara
          if-no-files-found: ignore

      - name: Upload coverage reports to Codecov
        uses: codecov/codecov-action@v4.0.1
        with:
          token: ${{ secrets.CODECOV_TOKEN }}


================================================
FILE: .github/workflows/release_notifications.yml
================================================
name: Release Notifications

on:
  workflow_run:
    workflows: ["Docker image build and push"]
    types: [completed]

permissions:
  contents: read

jobs:
  notify:
    runs-on: ubuntu-latest
    # Only run when build succeeded and was triggered by a release
    if: >
      github.event.workflow_run.conclusion == 'success' &&
      github.event.workflow_run.event == 'release'

    steps:
      - uses: actions/checkout@v4

      - name: Get release info
        id: version
        env:
          GH_TOKEN: ${{ github.token }}
          HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}
          REPO: ${{ github.repository }}
        run: |
          # The head_branch of a release-triggered workflow is the tag name
          TAG="$HEAD_BRANCH"

          # Fetch release details by tag
          RELEASE_JSON=$(gh api "repos/$REPO/releases/tags/$TAG" 2>/dev/null || true)

          if [ -z "$RELEASE_JSON" ]; then
            echo "Could not find release for tag $TAG, trying latest"
            RELEASE_JSON=$(gh api "repos/$REPO/releases/latest")
            TAG=$(echo "$RELEASE_JSON" | jq -r '.tag_name')
          fi

          URL=$(echo "$RELEASE_JSON" | jq -r '.html_url')
          PRERELEASE=$(echo "$RELEASE_JSON" | jq -r '.prerelease')

          echo "tag=$TAG" >> $GITHUB_OUTPUT
          echo "url=$URL" >> $GITHUB_OUTPUT
          echo "prerelease=$PRERELEASE" >> $GITHUB_OUTPUT
          echo "Release: $TAG (prerelease: $PRERELEASE)"

      - name: Extract changelog for version
        if: steps.version.outputs.prerelease != 'true'
        id: changelog
        env:
          VERSION: ${{ steps.version.outputs.tag }}
        run: |
          # Remove 'v' prefix if present for matching changelog headers
          VERSION_NUM="${VERSION#v}"

          # Extract section between this version and the next version header
          # The changelog format is: # [version] - date
          NOTES=$(sed -n "/^# \[$VERSION_NUM\]/,/^# \[/p" CHANGELOG.md | sed '$d' | tail -n +2)

          # Store in output (handle multiline)
          echo "notes<<EOF" >> $GITHUB_OUTPUT
          echo "$NOTES" >> $GITHUB_OUTPUT
          echo "EOF" >> $GITHUB_OUTPUT

      - name: Generate summary
        if: steps.version.outputs.prerelease != 'true'
        id: summary
        env:
          NOTES: ${{ steps.changelog.outputs.notes }}
        run: |
          # Count items in each section using stateful awk to properly track sections
          ADDED=$(echo "$NOTES" | awk '/^## Added/{f=1;next} /^## [A-Z]/{f=0} f&&/^- /{c++} END{print c+0}')
          FIXED=$(echo "$NOTES" | awk '/^## Fixed/{f=1;next} /^## [A-Z]/{f=0} f&&/^- /{c++} END{print c+0}')
          CHANGED=$(echo "$NOTES" | awk '/^## Changed/{f=1;next} /^## [A-Z]/{f=0} f&&/^- /{c++} END{print c+0}')
          REMOVED=$(echo "$NOTES" | awk '/^## Removed/{f=1;next} /^## [A-Z]/{f=0} f&&/^- /{c++} END{print c+0}')

          # Build summary line
          PARTS=""
          [ "$ADDED" -gt 0 ] && PARTS="$ADDED new feature$([ "$ADDED" -gt 1 ] && echo 's')"
          [ "$FIXED" -gt 0 ] && PARTS="${PARTS:+$PARTS, }$FIXED fix$([ "$FIXED" -gt 1 ] && echo 'es')"
          [ "$CHANGED" -gt 0 ] && PARTS="${PARTS:+$PARTS, }$CHANGED improvement$([ "$CHANGED" -gt 1 ] && echo 's')"
          [ "$REMOVED" -gt 0 ] && PARTS="${PARTS:+$PARTS, }$REMOVED removal$([ "$REMOVED" -gt 1 ] && echo 's')"

          # Default if nothing found
          [ -z "$PARTS" ] && PARTS="various updates"

          echo "summary=$PARTS" >> $GITHUB_OUTPUT

      - name: Post to Discord
        if: steps.version.outputs.prerelease != 'true'
        env:
          DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_URL }}
          VERSION: ${{ steps.version.outputs.tag }}
          RELEASE_URL: ${{ steps.version.outputs.url }}
          SUMMARY: ${{ steps.summary.outputs.summary }}
        run: |
          # Discord embed with summary
          curl -H "Content-Type: application/json" \
               -d "{
                 \"embeds\": [{
                   \"title\": \"🚀 Dawarich $VERSION Released!\",
                   \"url\": \"$RELEASE_URL\",
                   \"color\": 5814783,
                   \"description\": \"A new version of Dawarich is available!\\n\\n**This release:** $SUMMARY\\n\\n[📋 View full changelog]($RELEASE_URL)\",
                   \"footer\": {
                     \"text\": \"docker pull freikin/dawarich:$VERSION\"
                   }
                 }]
               }" \
               "$DISCORD_WEBHOOK"

      - name: Post to Mastodon
        if: steps.version.outputs.prerelease != 'true'
        env:
          MASTODON_ACCESS_TOKEN: ${{ secrets.MASTODON_ACCESS_TOKEN }}
          VERSION: ${{ steps.version.outputs.tag }}
          RELEASE_URL: ${{ steps.version.outputs.url }}
          SUMMARY: ${{ steps.summary.outputs.summary }}
        run: |
          # Build status message using printf for proper newlines
          STATUS=$(printf '%s\n\n%s\n\n%s\n%s\n\n%s' \
            "🚀 Dawarich $VERSION is out!" \
            "This release: $SUMMARY" \
            "📦 docker pull freikin/dawarich:$VERSION" \
            "📋 Changelog: $RELEASE_URL" \
            "#Dawarich #SelfHosted #LocationTracking #Privacy #OpenSource")

          curl -X POST \
               -H "Authorization: Bearer $MASTODON_ACCESS_TOKEN" \
               -F "status=$STATUS" \
               "https://mastodon.social/api/v1/statuses"


================================================
FILE: .github/workflows/rubocop.yml
================================================
name: RuboCop
on:
  push:
  pull_request:

permissions:
  contents: read

jobs:
  rubocop:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v6
        with:
          fetch-depth: 0
      - name: Determine base ref
        id: base
        env:
          BASE_REF: ${{ github.base_ref }}
          EVENT_NAME: ${{ github.event_name }}
        run: |
          if [ "$EVENT_NAME" = "pull_request" ]; then
            CURRENT=$(git rev-parse --abbrev-ref HEAD)
            if [ "$BASE_REF" != "$CURRENT" ]; then
              git fetch origin "$BASE_REF":"$BASE_REF"
            fi
            echo "ref=$BASE_REF" >> "$GITHUB_OUTPUT"
          else
            echo "ref=HEAD~1" >> "$GITHUB_OUTPUT"
          fi
      - name: Set up Ruby
        uses: ruby/setup-ruby@v1
        with:
          bundler-cache: true
      - name: Run RuboCop on changed files
        env:
          BASE: ${{ steps.base.outputs.ref }}
        run: |
          # Get list of added/modified Ruby files compared to base ref
          FILES=$(git diff --name-only --diff-filter=AM "$BASE"...HEAD | grep -E '\.(rb|rake)$|^Gemfile$|^Rakefile$' | xargs)

          if [ -n "$FILES" ]; then
            bundle exec rubocop --force-exclusion --format github $FILES
          else
            echo "No Ruby files changed."
          fi


================================================
FILE: .gitignore
================================================
# See https://help.github.com/articles/ignoring-files for more about ignoring files.
#
# If you find yourself ignoring temporary files generated by your text editor
# or operating system, you probably want to add a global ignore instead:
#   git config --global core.excludesfile '~/.gitignore_global'

# Ignore bundler config.
/.bundle

# Ignore all logfiles and tempfiles.
/log/*
/tmp/*
!/log/.keep
!/tmp/.keep

# Ignore pidfiles, but keep the directory.
/tmp/pids/*
!/tmp/pids/
!/tmp/pids/.keep

# Ignore uploaded files in development.
/storage/*
!/storage/.keep
/tmp/storage/*
!/tmp/storage/
!/tmp/storage/.keep
/tmp/imports/*
!/tmp/imports/
/tmp/imports/watched/*
!/tmp/imports/watched/
!/tmp/imports/watched/.keep
!/tmp/imports/watched/put-your-directory-here.txt


/public/assets

# Ignore all files under /public/exports except the .keep file
/public/exports/*
!/public/exports/.keep
!/public/exports/

# Ignore all files under /public/imports, but keep .keep files and the watched directory
/public/imports/*
!/public/imports/.keep

# Ignore master key for decrypting credentials and more.
/config/master.key
/coverage
/node_modules

!/app/assets/builds/.keep
.DS_Store
.env

.byebug_history


.devcontainer/.onCreateCommandMarker
.devcontainer/.postCreateCommandMarker
.devcontainer/.updateContentCommandMarker

.vscode-server/
.ash_history
.cache/
.dotnet/
.cursorrules
.cursormemory.md
.serena/**/*

/config/credentials/production.key
/config/credentials/production.yml.enc
/config/credentials/staging.key
/config/credentials/staging.yml.enc

Makefile

/db/*.sqlite3
/db/*.sqlite3-shm
/db/*.sqlite3-wal

# Playwright
node_modules/
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
/e2e/temp/

.claude
.worktrees/


================================================
FILE: .rspec
================================================
--require spec_helper
--profile


================================================
FILE: .rubocop.yml
================================================
AllCops:
  NewCops: disable
  Exclude:
    - 'db/schema.rb'
plugins: rubocop-rails

Style/Documentation:
  Enabled: false

Style/ClassAndModuleChildren:
  Enabled: false

Layout/HashAlignment:
  Enabled: false

Metrics/BlockLength:
  Enabled: false

Metrics/MethodLength:
  Enabled: false

Metrics/AbcSize:
  Enabled: false

Metrics/CyclomaticComplexity:
  Enabled: false

Metrics/PerceivedComplexity:
  Enabled: false

Metrics/ModuleLength:
  Enabled: false

Metrics/ParameterLists:
  Enabled: false

Metrics/ClassLength:
  Enabled: false

Naming/VariableNumber:
  Exclude:
    - 'spec/**/*'

Rails/UniqueValidationWithoutIndex:
  Enabled: false

Rails/SkipsModelValidations:
  Enabled: false

Rails/BulkChangeTable:
  Exclude:
    - 'db/migrate/**/*'

Rails/ReversibleMigration:
  Exclude:
    - 'db/migrate/**/*'

Rails/NotNullColumn:
  Exclude:
    - 'db/migrate/**/*'

Lint/ConstantDefinitionInBlock:
  Exclude:
    - 'lib/tasks/**/*'

Lint/UnreachableCode:
  Exclude:
    - 'db/data/**/*'

Rails/LexicallyScopedActionFilter:
  Enabled: false

Rails/OutputSafety:
  Enabled: false

Naming/AccessorMethodName:
  Enabled: false

Naming/PredicatePrefix:
  Enabled: false

Layout/LineLength:
  Exclude:
    - 'config/initializers/devise.rb'


================================================
FILE: .ruby-version
================================================
3.4.6


================================================
FILE: AGENTS.md
================================================
# Repository Guidelines

## Project Structure & Module Organization
Dawarich is a Rails 8 monolith. Controllers, models, jobs, services, policies, and Stimulus/Turbo JS live in `app/`, while shared POROs sit in `lib/`. Configuration, credentials, and cron/Sidekiq settings live in `config/`; API documentation assets are in `swagger/`. Database migrations and seeds live in `db/`, Docker tooling sits in `docker/`, and docs or media live in `docs/` and `screenshots/`. Runtime artifacts in `storage/`, `tmp/`, and `log/` stay untracked.

## Architecture & Key Services
The stack pairs Rails 8 with PostgreSQL + PostGIS, Redis-backed Sidekiq, Devise/Pundit, Tailwind + DaisyUI, and Leaflet/Chartkick. Imports, exports, sharing, and trip analytics lean on PostGIS geometries plus workers, so queue anything non-trivial instead of blocking requests.

## Build, Test, and Development Commands
- `docker compose -f docker/docker-compose.yml up` — launches the full stack for smoke tests.
- `bundle exec rails db:prepare` — create/migrate the PostGIS database.
- `bundle exec bin/dev` and `bundle exec sidekiq` — start the web/Vite/Tailwind stack and workers locally.
- `make test` — runs Playwright (`npx playwright test e2e --workers=1`) then `bundle exec rspec`.
- `bundle exec rubocop` / `npx prettier --check app/javascript` — enforce formatting before commits.

## Coding Style & Naming Conventions
Use two-space indentation, snake_case filenames, and CamelCase classes. Keep Stimulus controllers under `app/javascript/controllers/*_controller.ts` so names match DOM `data-controller` hooks. Prefer service objects in `app/services/` for multi-step imports/exports, and let migrations named like `202405061210_add_indexes_to_events` manage schema changes. Follow Tailwind ordering conventions and avoid bespoke CSS unless necessary.

## Testing Guidelines
RSpec mirrors the app hierarchy inside `spec/` with files suffixed `_spec.rb`; rely on FactoryBot/FFaker for data, WebMock for HTTP, and SimpleCov for coverage. Browser journeys live in `e2e/` and should use `data-testid` selectors plus seeded demo data to reset state. Run `make test` before pushing and document intentional gaps when coverage dips.

## Commit & Pull Request Guidelines
Write short, imperative commit subjects (`Add globe_projection setting`) and include the PR/issue reference like `(#2138)` when relevant. Target `dev`, describe migrations, configs, and verification steps, and attach screenshots or curl examples for UI/API work. Link related Discussions for larger changes and request review from domain owners (imports, sharing, trips, etc.).

## Security & Configuration Tips
Start from `.env.example` or `.env.template` and store secrets in encrypted Rails credentials; never commit files from `gps-env/` or real trace data. Rotate API keys, scrub sensitive coordinates in fixtures, and use the synthetic traces in `db/seeds.rb` when demonstrating imports.


================================================
FILE: CHANGELOG.md
================================================
# Changelog
All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## [1.3.4] - 2026-03-15

### Changed

- Redesigned onboarding modal with two paths: "I have data" (inline file import) and "Start tracking" (app download + QR code). New users with existing location data can now start importing within 2 clicks of signing up.
- Onboarding completion is now persisted server-side (`settings.onboarding_completed`) instead of relying solely on localStorage, preventing the modal from reappearing after browser data clears.
- Route opacity data migration now runs as a background job instead of inline during migration, improving deployment reliability for large instances.

### Fixed

- Fix admin and supporter tooltip overflowing the page on narrow screens. #1449
- Fix date navigation arrow tooltips overlapping with the navbar on map pages. #2229 #2100
- Fix infinite loading spinner when a trip has no points in its date range. #2293
- Fix Insights monthly digest panels disappearing when switching months. #2305
- Fix suggested visit confirm/decline not removing the visit from the list. #2307
- Fix Stats page reloading when clicking "countries, cities" link. #2270
- Fix map base layer selection not being restored after page reload (Maps v1). #2093
- Fix duplicate country names in stats caused by geocoder returning different spellings. #2044
- Fix total distance display overlapping layer picker when distance is in miles. #2017
- Fix default route opacity displaying as 6000% for new users. #1891
- Fix shared month stats map missing hexagons from the last day of the month. #1934
- Fix Nominatim reverse geocoder producing all places named "Suggested place" instead of actual place names. #2182
- Fix IDL-crossing route segmenter returning inconsistent coordinate types. `unwrapCoordinates` now always returns a uniform array-of-arrays structure. #2038
- Fix a migration taking too long. #2375
- Fix family sharing not including the requesting user's own location. #2153
- The "Destroy" button on the trip page is now orange. #2348

## [1.3.3] - 2026-03-12

### Added

- Better user management with pagination, search, and filtering in the admin panel. Admins can now easily find and manage users based on email, registration date, and activity status.

### Fixed

- Points table now converts speed from m/s to km/h (or mph) using the user's distance unit preference. Previously raw m/s values were displayed with a "km/h" label. #2337
- Digest list API (`GET /api/v1/digests`) now returns distance as a structured object with `meters`, `converted`, and `unit` fields, matching the detail endpoint. Previously it returned raw meters, causing clients to display incorrect values. **Breaking change**: the `distance` field changed from an integer to an object. #2336
- Dead documentation links in v0.26.0 changelog entry now point to the correct URLs. #2344
- Filter out Immich and Photoprism api keys from logs to prevent accidental exposure. #2368
- Fix foreign key violation when deleting users with place_visits referencing visits.
- Fix reverse geocoding job failing on points with nil timestamp or lonlat.
- Fix unsupported archive format generating Sentry noise instead of a user-friendly notification.
- Fix deadlock in reverse geocoding places upsert under concurrent Sidekiq workers.
- Reduce Redis disk I/O by relaxing RDB snapshot frequency. Previously the default `save 60 10000` rule caused a snapshot every ~60 seconds due to Sidekiq polling, generating tens of terabytes of disk writes over weeks. New defaults: snapshots every 15 minutes (10+ changes) or 5 minutes (100+ changes).
- Reduce default Sidekiq concurrency from 10 to 5 threads. Most self-hosted instances don't need 10 workers and the extra threads increase Redis polling traffic.
- Migration bug for version skippers. #2362

## [1.3.2] - 2026-03-08

**Important**: Self-hosters are not limited in any way. All features remain fully available regardless of plan. The new Lite plan and related limitations apply only to Dawarich Cloud users. If you're self-hosting, you can ignore the Lite plan details below. Self-hosted instances will continue to have access to all features without any restrictions.

### Added

- Lite plan for Dawarich Cloud. Lite includes core tracking, map visualization (routes, points), stats, and the read API. Data view is limited to the last 12 months — older data is archived but can always be exported. Pro-only features: Heatmap, Fog of War, Scratch Map, Globe View, Immich/Photoprism integrations, public stats sharing, and write API (update/delete). Lite users can still create points via the API. Self-hosted instances are unaffected — all features remain fully available regardless of plan.
- Timed layer previews for Lite users on the map. Toggling a Pro-only layer (Heatmap, Fog of War, Scratch Map) shows it for 20 seconds with a countdown, then auto-hides with an upgrade prompt.
- Per-plan API rate limiting via `rack-attack`. Lite: 200 requests/hour, Pro: 1,000 requests/hour. Self-hosted instances are exempt. Rate-limited responses return 429 with `Retry-After` header.
- Archival warning notifications for Lite users approaching the 12-month data window: in-app notification at 11 months, email at 11.5 months, archived confirmation at 12 months.
- `GET /api/v1/plan` endpoint returning the user's current plan and feature availability.
- `X-Total-Points-In-Range` and `X-Scoped-Points` response headers on the points API, allowing clients to detect when data is being windowed.
- Branded OAuth buttons for Google and GitHub on the login page.

### Changed

- Numeric-only strings passed to timestamp API parameters (e.g. `start_at`, `end_at`) are now treated as Unix timestamps directly. Previously they were passed through `Time.zone.parse`, which could return unexpected results. If you were relying on the old behavior for numeric strings, update your API calls accordingly.
- The user serializer now includes `plan` in the `subscription` object.

## [1.3.1] - 2026-02-27

### Changed

- User deletion now being done in the background to prevent request timeouts for users with large amount of data.

### Fixed

- Point speed in Map V2 is now correctly calculated from m/s to km/h or mph based on user preference. #2308
- Family members are now being loaded correctly on Map V2 when family layer is enabled. #2250
- Photos popups on Map V2 now show the photo timestamp in user's timezone. #2310
- Fix the issue preventing fresh app from starting. #2304

## [1.3.0] - 2026-02-25

The Storage & Timeline Interaction Release

This release adds a dedicated `motion_data` column for transportation-relevant fields alongside the existing `raw_data`. Users can now set their timezone for accurate date/time display across the app. The Timeline feed in Map v2 gains richer map interaction: hovering a journey highlights its track with an animated border, clicking zooms to fit and selects it, and expanding a day shows visit markers even when the Visits layer is off. User data export/import is enhanced with a new v2 format using JSONL files and monthly splitting for large datasets, while remaining backward-compatible with the old format.

### Added

- Per-user timezone setting. Users can now select their timezone from Settings > General, and all dates/times across the app (including background jobs and API responses) will respect it. Defaults to the server's `TIME_ZONE` environment variable for existing users.
- `motion_data` JSONB column on the `points` table for storing transportation-relevant fields separately from `raw_data`.
- Background job (`DataMigrations::BackfillMotionDataJob`) to backfill `motion_data` from `raw_data` for existing points.
- New Timeline feed in Map v2 Tools panel for browsing daily location history. Distances and speeds respect the user's distance unit preference (km/mi).
- Clicking a track point (when "Show Points" is enabled) now displays point info (timestamp, battery, altitude, speed) in the track info panel instead of triggering a position update. Dragging a point still updates its position and triggers track recalculation.
- Timeline-map interaction: hovering a journey entry in the Timeline feed now highlights the matching track on the map with the animated border and flow effect. Clicking a journey entry zooms the map to fit the track and keeps it selected. Expanding a day in the Timeline now temporarily shows visit markers for that day, even if the Visits layer is disabled.
- AES-256-GCM encryption for raw data archives (format version 2). Set `ARCHIVE_ENCRYPTION_KEY` to use a custom key; otherwise derives from `SECRET_KEY_BASE`. Existing unencrypted archives (format version 1) are read transparently.
- v2 export/import format with JSONL files and monthly splitting for large entities (points, visits, stats, tracks, digests). The new format streams data to avoid memory issues with large datasets, while remaining backward-compatible with v1 archives (`data.json`).
- User data export now includes Tags, Taggings, Tracks (with embedded TrackSegments), Digests, and Raw Data Archives — previously missing from export/import, meaning users who exported and re-imported would lose these entities.
- Tracks are exported with their `original_path` serialized as WKT and `track_segments` embedded as a nested array, preserving transportation mode detection data across export/import cycles.
- Digests get a fresh `sharing_uuid` on import for security — old share links from the original user won't work for the importing user.
- Raw Data Archives are exported with their attached gzip files, enabling full data restoration.
- Failed imports now will have an error message shown to the user.
- Pagination now looks nicer and more informative, indicating current page. #2279
- Imports and exports now can be sorted by name, file size, number of points, and creation date. #2279
- Lots of missing Swagger specs for the API endpoints have been added, improving API documentation and enabling better client generation. swagger.yaml is updated.

### Changed

- Transportation-relevant fields (motion, activity, action) are now stored in a dedicated `motion_data` column alongside `raw_data`, enabling efficient transportation mode detection.
- All import sources now write both `raw_data` (full original payload) and `motion_data` (transportation-relevant fields).
- The `STORE_GEODATA` setting now correctly controls whether geodata is written during reverse geocoding.
- Dropped unused `idx_points_user_city` database index (304 MB) and replaced the full `reverse_geocoded_at` index (1,149 MB) with a smaller partial index covering only un-geocoded rows.
- Selecting a track on Map v2 now always dims other tracks, regardless of whether the track has transportation mode segments.
- Default map layers for new users changed from Routes + Heatmap to Tracks + Heatmap. Existing users' settings are unaffected.
- Renamed the bottom-panel "Timeline" feature to "Replay" to avoid naming collision with the new Timeline feed sidebar.
- Default value for `RAILS_ENV` in `docker-compose.yml` is now `production` instead of `development`

### Fixed

- Stats queries (daily distance, time of day) now correctly handle timezone conversion without double-converting from UTC.
- Timezone validation in stats queries now properly resolves Rails timezone names to IANA identifiers.
- Clicking on [Map] on Stats page now correctly respects the user's preferred map version (v1 or v2) instead of always linking to Map v1. #2281


## [1.2.0] - 2026-02-15

### Changed

- Overall app performance in browser was improved
- Docker images are now being built in parallel for both amd64 and arm64 architectures to speed up the build process. Thank you @rtuszik!

### Added

- Map v2 requires WebGL support, so if user's browser doesn't support it or it's disabled, they will see a warning message with a link to the list of supported browsers.
- New **Insights API** (`GET /api/v1/insights`) returning year overview with totals, activity heatmap, and streak data for the mobile app.
- New **Insights Details API** (`GET /api/v1/insights/details`) returning year-over-year comparison and travel patterns for the mobile app.
- New **Digests API** (`GET/POST/DELETE /api/v1/digests`) allowing the mobile app to list, view, generate, and delete yearly digests. Digest generation runs asynchronously via Sidekiq and returns `202 Accepted`. Digest detail supports conditional GET (`Last-Modified` / `304 Not Modified`).

### Fixed

- Scratch map layer is now working again on Map v2.
- Colored routes on Map v2 are now working correctly. Zoom in closer to see colored segments. #2254
- Live mode on Map v2 is now working again.

## [1.1.0] - 2026-02-08

The Timeline Release

In Map V2 Tools, user can now enable Timeline tool, which allows to quickly navigate through time and see how their location changed throughout the day. It can also be used to replay a trip by clicking the play button. Timeline tool always spans across 24 hours, but you can change the date by clicking on the date picker. Timeline tool is available only on Map V2.

### Added

- Photos are now being clustered on the Map v2 to improve performance and usability when viewing large numbers of photos.
- City statistics thresholds are now user-configurable: "Min Minutes in City" and "Max Gap Between Points" sliders in the Map v2 Settings panel. #2207
- New Timeline tool is added to Map V2. It allows user to quickly navigate through time and see how their location changed throughout the day. It can also be used to replay a trip by clicking the play button.

### Fixed

- The SSL Security Warning is now working correctly on the Immich and Photoprism integration forms.
- Family members and Places layers are now being correctly remembered across page reloads on Map v2.
- Immich returning 400. #2222 #2186
- Points info on the Map V2 now shows time in 24h format and includes seconds. #2172
- Digests not being created for years earlier than 2000. #2158
- Tracks on Map V2 are now respecting the date filters correctly. #2196
- Undefined method `.to_sym` for nil in Sidekiq. #2190
- `/api/v1/stats` now works faster.

### Changed

- Zooming animation is disabled on Map V2 loading #2219
- Exporting points to GPX and GeoJSON now works better and faster for large numbers of points by processing the export in chunks to reduce memory usage. #2161


## [1.0.4] - 2026-02-01

### Fixed

- Wrong path helper in the navbar for Settings link. #2215 #2213


## [1.0.3] - 2026-02-01

### Fixed

- Gemfile being not updated #2210
- Excessive memory usage during visits suggestions job (thanks @nareddyt!) #2119

### Added

- `SMTP_STARTTLS` environment variable to enable STARTTLS for SMTP connections. Disabled by default.

## [1.0.2] - 2026-01-31

The Insights, Transportation Mode Detection and Supporter Verification release

Quiet a few big things in this release! It starterted with the idea of adding the Insights page. I experimented with it a bit to see what kinds charts and visualizations we can already have based on the existing data. There were some, but one of the most exciting to me would be the ability to see the Activity Breakdown: now many hours I spent walking, driving and running. Spoiler: I didn't run that much last year :) Anyway, to get that, we needed to have transportation mode detection for tracks. So naturally I went ahead and implemented that as well. Now, not only we can see the activity breakdown, but also, on the Map V2, if you click on a track (Tracks layer should enabled), you will see the transportation modes for it. That's what I wanted for Dawarich for a long time, and I'm happy it's finally here! In the map settings panel, there is now Transportation Mode Detection section, where you can configure speed thresholds for each mode. By default, they are set to reasonable values, but you can tweak them as you wish. Changing the thresholds will recalculate modes for all tracks in the background, which may take a while depending on how many tracks you have.

Another thing introduced in this release, is support verification. Almost 150 people have supported us financially on [Ko-fi](https://ko-fi.com/freika), [Patreon](https://www.patreon.com/freika) and [GitHub Sponsors](https://github.com/sponsors/Freika/), and if you're one of them, on the Settings page you can now enter your email and verify your support. Verified supporters will get a special (disableable) badge in the navbar as a token of our appreciation. Thank you so much for supporting Dawarich!

Anyway, enjoy the release and don't forget to report any bugs you may find!

### Added

- App-level DNS cache with 5 minutes TTL to reduce DNS lookups and improve performance. #2183
- New **Insights page** with comprehensive analytics and visualizations:
  - **Activity heatmap**: GitHub-style contribution graph showing daily activity throughout the year
  - **Activity streak**: Track your current streak and longest streak of consecutive active days
  - **Top visited locations**: See your most frequently visited places for the selected year
  - **Year comparison**: Compare stats (distance, countries, cities, active days) with previous year
  - **Activity breakdown**: Visualize your activity distribution by transportation mode
  - **Monthly digest**: Detailed monthly statistics with travel patterns
  - **Travel patterns**: Time-of-day and day-of-week activity distribution
  - **Movement wellness**: Health-related insights based on your movement data
  - **Location clusters**: Geographic clustering of your visited locations
- **Transportation mode detection for tracks**: Tracks are now automatically segmented by transportation mode (walking, cycling, driving, etc.) with configurable speed thresholds in settings. Modes are recalculated when threshold settings change.
- **Near real-time track generation**: Tracks are now generated within ~45 seconds of receiving new points (via OwnTracks, Overland, or the Points API) using a Redis-based debouncer. This replaces the previous 4-hour polling cycle for most cases. Daily generation job frequency reduced from every 4 hours to every 12 hours as a fallback.
- **Track merging**: Consecutive tracks that belong to the same journey are automatically merged when the gap between them is within the configured time threshold.
- Email preferences moved to "General" tab in user settings for better organization.

### Fixed

- Remove assets before precompilation to prevent stale assets from being served. #2187
- undefined method 'to_sym' for nil in sidekiq #2190
- `Tracks::BoundaryResolverJob` now uses deterministic exponential backoff instead of random delays, and stops retrying after 5 attempts to avoid infinite rescheduling.
- Hanging Sidekiq job #2134

### Changed

- Daily track generation job runs every 12 hours instead of every 4 hours, since real-time generation handles most cases.


## [1.0.1] - 2026-01-24

### Added

- SSL certificate verification can now be disabled for Immich and Photoprism integrations to support self-signed certificates. A prominent security warning is displayed when this option is enabled. #1645

### Fixed

- Photo timestamps from Immich are now correctly parsed as UTC, fixing the double timezone offset bug where times were displayed incorrectly. #1752
- Trip photo grids now update immediately after photos are imported, instead of showing cached/stale results for up to 24 hours. #627 #988
- Immich API responses are now validated for content-type and JSON format before parsing, providing clear diagnostic error messages when the API returns unexpected responses. #698 #1013 #1078
- Response validator logs truncated response bodies (max 1000 chars) when JSON parsing fails, improving debugging capabilities.
- GeoJSON formatted points now have correct timestamp parsed from `raw_data['properties']['date']` field.
- Reduce number of iterations during cache cleaning to improve performance.
- Version in the navbar is now correct. #2154
- Dawarich can now be ran under a non-root user in Docker. #1159
- Fix an error on the Trips page when trip is created but no path is yet calculated. #1426
- Catch an error with invalid response during reverse-geocoding. #1439
- In the Immich integration form there are now required permissions listed: `asset.read` and `asset.view`. #1730
- A doc issue regarding suggesting new visits. #1737
- `ALLOW_EMAIL_PASSWORD_REGISTRATION` and `OIDC_AUTO_REGISTER` env vars are now being respected correctly. #1972
- Fog of War layer on Map V1 now properly re-appears when toggled off and on again without requiring a page refresh. #2039
- User's `points_count` counter cache is now properly updated when creating points via OwnTracks, Overland, and generic Points API. This fixes visit suggestions not working for users using HomeAssistant or similar integrations. #2167
- Removed redundant subscriptions to WS channel.
- Live mode is working again on both map V1 and V2.

### Changed

- Map V2 is now the default map version for new users. Existing users will keep using Map V1 unless they change it in the settings.
- Email preferences moved to dedicated "Emails" tab in user settings for better organization.

### Removed

- Tile Usage reporting feature and related prometheus metric have been removed due to low usage. #1876


## [1.0.0] - 2026-01-20

The 1.0.0 release. Same as in 0.37.3, but with updated version number. We're aiming to provide more stable releases going forward.

All the issues that are currently open in Github will be addressed in the upcoming releases.


## [0.37.3] - 2026-01-11

### Fixed

- Routes are now being drawn the very same way on Map V2 as in Map V1. #2132 #2086 #2121
- RailsPulse performance monitoring is now disabled for self-hosted instances. It fixes poor performance on Synology. #2139 #2096

### Changed

- Map V2 points loading is significantly sped up.
- Points size on Map V2 was reduced to prevent overlapping. #2122
- Points sent from Owntracks and Overland are now being created synchronously to instantly reflect success or failure of point creation.

## [0.37.2] - 2026-01-04

### Fixed

- Months are now correctly ordered (Jan-Dec) in the year-end digest chart instead of being sorted alphabetically.
- Time spent in a country and city is now calculated correctly for the year-end digest email. #2104
- Updated Trix to fix a XSS vulnerability. #2102
- Map v2 UI no longer blocks when Immich/Photoprism integration has a bad URL or is unreachable. Added 10-second timeout to photo API requests and improved error handling to prevent UI freezing during initial load. #2085

### Added
- In Map v2 settings, you can now enable map to be rendered as a globe.

## [0.37.1] - 2025-12-30

### Fixed

- The db migration preventing the app from starting.
- Raw data archive verifier now allows having points deleted from the db after archiving.

## [0.37.0] - 2025-12-30

### Added

- In the beginning of the year users will receive a year-end digest email with stats about their tracking activity during the past year. Users can opt out of receiving these emails in User Settings -> Notifications. Emails won't be sent if no email is configured in the SMTP settings or if user has no points tracked during the year.

### Changed

- Added and removed some indexes to improve the app performance based on the production usage data.

### Changed

- Deleting an import will now be processed in the background to prevent request timeouts for large imports.

### Fixed

- Deleting an import will no longer result in negative points count for the user.
- Updating stats. #2022
- Validate trip start date to be earlier than end date. #2057
- Fog of war radius slider in map v2 settings is now being respected correctly. #2041
- Applying changes in map v2 settings now works correctly. #2041
- Invalidate stats cache on recalculation and other operations that change stats data.


## [0.36.4] - 2025-12-26

### Fixed

- Fixed a bug preventing the app to start if a composite index on stats table already exists. #2034 #2051 #2046
- New compiled assets will override old ones on app start to prevent serving stale assets.
- Number of points in stats should no longer go negative when points are deleted. #2054
- Disable Family::Invitations::CleanupJob no invitations are in the database. #2043
- User can now enable family layer in Maps v2 and center on family members by clicking their emails. #2036


## [0.36.3] - 2025-12-14

### Added

- Setting `ARCHIVE_RAW_DATA` env var to true will enable monthly raw data archiving for all users. It will look for points older than 2 months with `raw_data` column not empty and create a zip archive containing raw data files for each month. After successful archiving, raw data will be removed from the database to save space. Monthly archiving job is being run every day at 2:00 AM. Default env var value is false.
- In map v2, user can now move points when Points layer is enabled. #2024
- In map v2, routes are now being rendered using same logic as in map v1, route-length-wise. #2026

### Fixed

- Cities visited during a trip are now being calculated correctly. #547 #641 #1686 #1976
- Points on the map are now show time in user's timezone. #580 #1035 #1682
- Date range inputs now handle pre-epoch dates gracefully by clamping to valid PostgreSQL integer range. #685
- Redis client now also being configured so that it could connect via unix socket. #1970
- Importing KML files now creates points with correct timestamps. #1988
- Importing KMZ files now works correctly.
- Map settings are now being respected in map v2. #2012


## [0.36.2] - 2025-12-06

The Map v2 release

In this release we're introducing Map v2 based on MapLibre GL JS. It brings better performance, smoother interactions and more features in the future. User can select between Map v1 (Leaflet) and Map v2 (MapLibre GL JS) in the Settings -> Map Settings. New map features will be added to Map v2 only.

### Added

- User can select between Map v1 (Leaflet) and Map v2 (MapLibre GL JS) in the User Settings.

### Fixed

- Heatmap and Fog of War now are moving correctly during map interactions on v2 map. #1798
- Polyline crossing international date line now are rendered correctly on v2 map. #1162
- Place popup tags parsing (MapLibre GL JS compatibility)
- Stats calculation should be faster now.


## [0.36.1] - 2025-11-29

### Fixed

- Exporting user data now works a lot faster and consumes less memory.
- Fix the restart loop. #1937 #1975

## [0.36.0] - 2025-11-24

OIDC and KML support release

So, you want to configure your OIDC provider. If not — skip to the actual changelog. You're going to need to provide at least 4 environment variables: `OIDC_CLIENT_ID`, `OIDC_CLIENT_SECRET`, `OIDC_ISSUER`, and `OIDC_REDIRECT_URI`. Then, if you want to rename the provider from "OpenID Connect" to something else (e.g. "Authentik"), set `OIDC_PROVIDER_NAME` variable as well. If you want to disable email/password registration and allow only OIDC login, set `ALLOW_EMAIL_PASSWORD_REGISTRATION` to `false`. After just 7 brand new environment variables, you'll never have to deal with passwords in Dawarich again!

Jokes aside, even though I'm not a fan of bloating the environment with too many variables, this is a nice addition and it will be reused in the cloud version of Dawarich as well. Thanks for waiting more than a year for this feature!

To configure your OIDC provider, set the following environment variables:

```
OIDC_CLIENT_ID=client_id_example
OIDC_CLIENT_SECRET=client_secret_example
OIDC_ISSUER=https://authentik.yourdomain.com/application/o/dawarich/
OIDC_REDIRECT_URI=https://your-dawarich-url.com/users/auth/openid_connect/callback
OIDC_AUTO_REGISTER=true # optional, default is false
OIDC_PROVIDER_NAME=YourProviderName # optional, default is OpenID Connect
ALLOW_EMAIL_PASSWORD_REGISTRATION=false # optional, default is true
```

### Added

- Support for KML file uploads. #350
- Added a commented line in the `docker-compose.yml` file to use an alternative PostGIS image for ARM architecture.
- User can now create a place directly from the map and add tags and notes to it. If reverse geocoding is enabled, list of nearby places will be shown as suggestions.
- User can create and manage tags for places.
- Visits for manually created places are being suggested automatically, just like for areas.
- User can enable or disable places layers on the map to show/hide all or just some of their visited places based on tags.
- User can define privacy zones around places with specific tags to hide map data within a certain radius.
- If user has a place tagged with a tag named "Home" (case insensitive), and this place doesn't have a privacy zone defined, this place will be used as home location for days with no tracked data. #1659 #1575

### Fixed

- The map settings panel is now scrollable
- Fixed a bug where family location sharing settings were not being updated correctly. #1940

### Changed

- Internal redis settings updated to implement support for connecting to Redis via unix socket. #1706
- Implemented authentication via GitHub and Google for Dawarich Cloud.
- Implemented OpenID Connect authentication for self-hosted Dawarich instances. #66


## [0.35.1] - 2025-11-09

### Fixed

- StrongMigration issue #1931


## [0.35.0] - 2025-11-09

⚠️ Important ⚠️

The default `docker-compose.yml` file has been updated to provide sensible defaults for self-hosted production environments. This should not break existing setups, but it's recommended to review your `docker-compose.yml` file and update it accordingly.

You can now set `RAILS_ENV` environment variable to `production` to run Dawarich in production mode.

### Added

- Selection tool on the map now can select points that user can delete in bulk. #433

### Fixed

- Taiwan flag is now shown on its own instead of in combination with China flag.
- On the registration page and other user forms, if something goes wrong, error messages are now shown to the user.
- Leaving family, deleting family and cancelling invitations now prompt confirmation dialog to prevent accidental actions.
- Each pending family invitation now also contains a link to share with the invitee.

### Changed

- Removed useless system tests and cover map functionality with Playwright e2e tests instead.
- S3 storage now can be used in self-hosted instances as well. Set STORAGE_BACKEND environment variable to `s3` and provide `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`, `AWS_BUCKET` and `AWS_ENDPOINT_URL` environment variables to configure it.
- Number of family members on self-hosted instances is no longer limited. #1918
- Export to GPX now adds speed and course to each point if they are available.
- `docker-compose.yml` file updated to provide sensible defaults for self-hosted production environment.
- `.env.example` file added with default environment variables.
- Single Dockerfile introduced so Dawarich could be run in self-hosted mode in production environment.

## [0.34.2] - 2025-10-31

### Fixed

- Fixed a bug in UTM trackable concern. #1909

## [0.34.1] - 2025-10-30

### Fixed

- Broken Stats page for users with no reverse geocoding enabled. #1877

### Changed

- Date navigation on the map page is no longer shown as floating panel. It is now part of the top navigation bar to prevent overlapping with other map controls. #1894 #1881

### Added

- [Dawarich Cloud] Added support for UTM parameters during user registration. UTM parameters will be stored with the user record for marketing analytics purposes.

## [0.34.0] - 2025-10-10

The Family release

In this release we're introducing family features that allow users to create family groups, invite members, and share location data. Family owners can manage members, control sharing settings, and ensure secure access to shared information. Location sharing is optional and can be enabled or disabled by each member individually. Users can join only one family at a time. Location sharing settings can be set to share location for 1, 6, 12, 24 hours or permanently. Family features are now available only for self-hosted instances and will be available in the cloud in the future. When "Family members" layer is enabled on the map, family member markers will be updated in real-time.

### Added

- Users can now create family groups and invite members to join.

### Fixed

- Sign out button works again. #1844
- Fixed user deletion bug where user could not be deleted due to counter cache on points.
- Users always have default distance unit set to kilometers. #1832
- All confirmation dialogs are now showing only once.

### Changed

- Minor versions of Dawarich are being built for ARM64 architecture as well again. #1840
- Importing process for Google Maps Timeline exports, GeoJSON and geodata from photos is now significantly faster.
- The Map page now features a full-screen map.


## [0.33.1] - 2025-10-07

### Changed

- On the Trip page, instead of list of visited countries, a number of them is being shown. Clicking on it opens a modal with a list of countries visited during the trip. #1731

### Fixed

- `GET /api/v1/stats` endpoint now returns correct 0 instead of null if no points were tracked in the requested period.
- User import data now being streamed instead of loaded into memory all at once. This should prevent large imports from exhausting memory or hitting IO limits while reading export archives.
- Popup for manual visit creation now looks better in both light and dark modes. #1835
- Fixed a bug where visit circles were not interactive on the map page. #1833
- Fixed a bug with stats sharing settings being not filled. #1826
- Fixed a bug where user could not be deleted due to counter cache on points. #1818
- Introduce apt-get upgrade before installing new packages in the docker image to prevent vulnerabilities. #1793
- Fixed time shift when creating visits manually. #1679
- Provide default map layer if user settings are not set.

## [0.33.0] - 2025-09-29

### Fixed

- Fix a bug where some points from Owntracks were not being processed correctly which prevented import from being created. #1745
- Hexagons for the stats page are now being calculated a lot faster.
- Prometheus exporter is now not being started when console is being run.
- Stats will now properly reflect countries and cities visited after importing new points.
- `GET /api/v1/points` will now return correct latitude and longitude values. #1502
- Deleting an import will now trigger stats recalculation for affected months. #1789
- Importing process should now schedule visits suggestions job a lot faster.
- Importing GPX files that start with `<gpx` tag will now be detected correctly. #1775
- Buttons on the map now have correct contrast in both light and dark modes.

### Changed

- Onboarding modal window now features a link to the App Store and a QR code to configure the Dawarich iOS app.
- A permanent option was removed from stats sharing options. Now, stats can be shared for 1, 12 or 24 hours only.
- User data archive importing now uploads the file directly to the storage service instead of uploading it to the app first.
- Importing progress bars are now looking nice.
- Ruby version was updated to 3.4.6.

### Added

- Based on preferred theme (light or dark), the map controls will now load with the corresponding styles.
- [Dawarich Cloud] Added foundation for upcoming authentication from iOS app.
- [Dawarich Cloud] Trial users can now create up to 5 imports. After that, they will be prompted to subscribe to a paid plan.
- [Dawarich Cloud] Added Posthog analytics. Disabled by default, can be enabled with POSTHOG_ENABLED environment variable.


## [0.32.0] - 2025-09-13

### Fixed

- Tracked distance on year card on the Stats page will always be equal to the sum of distances on the monthly chart below it. #466
- Stats are now being calculated for trial users as well as active ones.

### Added

- A cron job to generate daily tracks for users with new points since their last track generation. Being run every 4 hours.
- A new month stat page, featuring insights on how user's month went: distance traveled, active days, countries visited and more.
- Month stat page can now be shared via public link. User can limit access to the page by sharing period: 1/12/24 hours or permanent.

### Changed

- Stats page now loads significantly faster due to caching.
- Data on the Stats page is being updated daily, except for total distance and number of geopoints tracked, which are being updated on the fly. Also, charts with yearly and monthly stats are being updated every hour.
- Minor versions are now being built only for amd64 architecture to speed up the build process.
- If user is not authorized to see a page, they will be redirected to the home page with appropriate message instead of seeing an error.

## [0.31.0] - 2025-09-04

The Search release

In this release we're introducing a new search feature that allows users to search for places and see when they visited them. On the map page, click on Search icon, enter a place name (e.g. "Alexanderplatz"), wait for suggestions to load, and click on the suggestion you want to search for. You then will see a list of years you visited that place. Click on the year to unfold list of visits for that year. Then click on the visit you want to see on the map and you will be moved to that visit on the map. From the opened visit popup you can create a new visit to save it in the database.

Important: This feature relies on reverse geocoding. Without reverse geocoding, the search feature will not work.

### Added

- User can now search for places and see when they visited them.

### Fixed

- Default value for `points_count` attribute is now set to 0 in the User model.

### Changed

- Tracks are not being calculated by server instead of the database. This feature is still in progress.


## [0.30.12] - 2025-08-26

### Fixed

- Number of user points is not being cached resulting in performance boost on certain pages and operations.
- Logout bug
- Api key is now shown even in trial period


## [0.30.11] - 2025-08-23

### Changed

- If user already have import with the same name, it will be appended with timestamp during the import process.

### Fixed

- Some types of imports were not being detected correctly and were failing to import. #1678


## [0.30.10] - 2025-08-22

### Added

- `POST /api/v1/visits` endpoint.
- User now can create visits manually on the map.
- User can now delete a visit by clicking on the delete button in the visit popup.
- Import failure now throws an internal server error.

### Changed

- Source of imports is now being detected automatically.


## [0.30.9] - 2025-08-19

### Changed

- Countries, visited during a trip, are now being calculated from points to improve performance.

### Added

- QR code for API key is implemented but hidden under feature flag until the iOS app supports it.
- X-Dawarich-Response and X-Dawarich-Version headers are now returned for all API responses.
- Trial version for cloud users is now available.


## [0.30.8] - 2025-08-01

### Fixed

- Fog of war is now working correctly on zoom and map movement. #1603
- Possibly fixed a bug where visits were no suggested correctly. #984
- Scratch map is now working correctly.


## [0.30.7] - 2025-08-01

### Fixed

- Photos layer is now working again on the map page. #1563 #1421 #1071 #889
- Suggested and Confirmed visits layers are now working again on the map page. #1443
- Fog of war is now working correctly. #1583
- Areas layer is now working correctly. #1583
- Live map doesn't cause memory leaks anymore. #880

### Added

- Logging for Photos layer is now enabled.
- E2e tests for map page.


## [0.30.6] - 2025-07-29

### Changed

- Put all jobs in their own queues.
- Visits page should load faster now.
- Reverse geocoding jobs now make less database queries.
- Country name is now being backfilled for all points. #1562
- Stats are now reflecting countries and cities. #1562

### Added
- Points now support discharging and connected_not_charging battery statuses. #768

### Fixed

- Fixed a bug where import or notification could have been accessed by a different user.
- Fixed a bug where draw control was not being added to the map when areas layer was enabled. #1583


## [0.30.5] - 2025-07-26

### Fixed

- Trips page now loads correctly.


## [0.30.4] - 2025-07-26

### Added

- Prometheus metrics are now available at `/metrics`. Configure `METRICS_USERNAME` and `METRICS_PASSWORD` environment variables for basic authentication, default values are `prometheus` for both. All other prometheus-related environment variables are also necessary.

### Fixed

- The Warden error in jobs is now fixed. #1556
- The Live Map setting is now respected.
- The Live Map info modal is now displayed. #665
- GPX from Basecamp is now supported. #790
- The "Delete Selected" button is now hidden when no points are selected. #1025


## [0.30.3] - 2025-07-23

### Changed

- Track generation is now significantly faster and less resource intensive.

### Fixed

- Distance on the stats page is now rounded. #1548
- Non-selfhosted users can now export and import their account data.


## [0.30.2] - 2025-07-22

### Fixed

- Stats calculation is now significantly faster.


## [0.30.1] - 2025-07-22

### Fixed

- Points limit exceeded check is now cached.
- Reverse geocoding for places is now significantly faster.

### Changed

- Stats page should load faster now.
- Track creation is temporarily disabled.


## [0.30.0] - 2025-07-21

⚠️ If you were using 0.29.2 RC, please run the following commands in the console, otherwise read on. ⚠️

```ruby
# This will delete all tracks 👇
Track.delete_all

# This will remove all tracks relations from points 👇
Point.update_all(track_id: nil)

# This will create tracks for all users 👇
User.find_each do |user|
  Tracks::CreateJob.perform_later(user.id, start_at: nil, end_at: nil, mode: :bulk)
end
```

### Added

- In the User Settings -> Background Jobs, you can now disable visits suggestions, which is enabled by default. It's a background task that runs every day around midnight. Disabling it might be useful if you don't want to receive visits suggestions or if you're using the Dawarich iOS app, which has its own visits suggestions.
- Tracks are now being calculated and stored in the database instead of being calculated on the fly in the browser. This will make the map page load faster.

### Changed

- Don't check for new version in production.
- Area popup styles are now more consistent.
- Notification about Photon API load is now disabled.
- All distance values are now stored in the database in meters. Conversion to user's preferred unit is done on the fly.
- Every night, Dawarich will try to fetch names for places and visits that don't have them. #1281 #902 #583 #212
- ⚠️ User settings are now being serialized in a more consistent way ⚠. `GET /api/v1/users/me` now returns the following data structure:
```json
{
  "user": {
    "email": "test@example.com",
    "theme": "light",
    "created_at": "2025-01-01T00:00:00Z",
    "updated_at": "2025-01-01T00:00:00Z",
    "settings": {
      "maps": {
        "url": "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
        "name": "Custom OpenStreetMap",
        "distance_unit": "km"
      },
      "fog_of_war_meters": 51,
      "meters_between_routes": 500,
      "preferred_map_layer": "Light",
      "speed_colored_routes": false,
      "points_rendering_mode": "raw",
      "minutes_between_routes": 30,
      "time_threshold_minutes": 30,
      "merge_threshold_minutes": 15,
      "live_map_enabled": false,
      "route_opacity": 0.3,
      "immich_url": "https://persistence-test-1752264458724.com",
      "photoprism_url": "",
      "visits_suggestions_enabled": true,
      "speed_color_scale": "0:#00ff00|15:#00ffff|30:#ff00ff|50:#ffff00|100:#ff3300",
      "fog_of_war_threshold": 5
    }
  }
}
```
- Links in emails will be based on the `DOMAIN` environment variable instead of `SMTP_DOMAIN`.

### Fixed

- Swagger documentation is now valid again.
- Invalid owntracks points are now ignored.
- An older Owntrack's .rec format is now also supported.
- Course and course accuracy are now rounded to 8 decimal places to fix the issue with points creation.

## [0.29.1] - 2025-07-02

### Fixed

- Buttons on the imports page now looks better in both light and dark mode. #1481
- The PROMETHEUS_EXPORTER_ENABLED environment variable default value is now "false", in quotes.
- The RAILS_CACHE_DB, RAILS_JOB_QUEUE_DB and RAILS_WS_DB environment variables can be used to set the Redis database number for caching, background jobs and websocket connections respectively. Default values are now 0, 1 and 2 respectively. #1420

### Changed

- Skip DNS rebinding protection for the health check endpoint.
- Added health check to app.json.

## [0.29.0] - 2025-07-02

You can now move your user data between Dawarich instances. Simply go to your Account settings and click on the "Export my data" button under the password section. An export will be created and you will be able to download it on Exports page once it's ready.

To import your data on a new Dawarich instance, create a new user and upload the exported zip file. You can import your data also on the Account page, by clicking "Import my data" button under the password section.

The feature is experimental and not yet aimed to replace a proper backup solution. Please use at your own risk.

### Added

- In the User Settings, you can now export your user data as a zip file. It will contain the following:
  - All your points
  - All your places
  - All your visits
  - All your areas
  - All your imports with files
  - All your exports with files
  - All your trips
  - All your notifications
  - All your stats

- In the User Settings, you can now import your user data from a zip file. It will import all the data from the zip file, listed above. It will also start stats recalculation.
- Export file size is now displayed in the exports and imports lists.
- A button to download an import file is now displayed in the imports list. It may not work properly for imports created before the 0.25.4 release.
- Imports now have statuses.

### Changed

- Oj is now being used for JSON serialization.

### Fixed

- Email links now use the SMTP domain if set. #1469



## [0.28.1] - 2025-06-11

### Fixed

- Limit notifications in navbar to 10. Fresh one will replace the oldest one. #1184

### Changed

- No osm point types are being ignored anymore.

## [0.28.0] - 2025-06-09

⚠️ This release includes a breaking change. ⚠️

_yet another, yay!_

Well, we're moving back to Sidekiq and Redis for background jobs and caching. Unfortunately, SolidQueue and SolidCache brought more problems than they solved. Please update your `docker-compose.yml` to use Redis and Sidekiq.

Before updating, you can remove `dawarich_development_queue` database from your postgres. All *.sqlite3 files in `dawarich_sqlite_data` volume can be removed as well.

```diff
networks:
  dawarich:
services:
+ dawarich_redis:
+   image: redis:7.4-alpine
+   container_name: dawarich_redis
+   command: redis-server
+   networks:
+     - dawarich
+   volumes:
+     - dawarich_shared:/data
+   restart: always
+   healthcheck:
+     test: [ "CMD", "redis-cli", "--raw", "incr", "ping" ]
+     interval: 10s
+     retries: 5
+     start_period: 30s
+     timeout: 10s
...
  dawarich_app:
    image: freikin/dawarich:latest
    container_name: dawarich_app
    volumes:
      - dawarich_public:/var/app/public
      - dawarich_watched:/var/app/tmp/imports/watched
      - dawarich_storage:/var/app/storage
      - dawarich_db_data:/dawarich_db_data
-     - dawarich_sqlite_data:/dawarich_sqlite_data
    ...
    restart: on-failure
    environment:
      RAILS_ENV: development
+     REDIS_URL: redis://dawarich_redis:6379
      DATABASE_HOST: dawarich_db
      DATABASE_USERNAME: postgres
      DATABASE_PASSWORD: password
      DATABASE_NAME: dawarich_development
-     # PostgreSQL database name for solid_queue
-     QUEUE_DATABASE_NAME: dawarich_development_queue
-     QUEUE_DATABASE_PASSWORD: password
-     QUEUE_DATABASE_USERNAME: postgres
-     QUEUE_DATABASE_HOST: dawarich_db
-     QUEUE_DATABASE_PORT: 5432
-     # SQLite database paths for cache and cable databases
-     CACHE_DATABASE_PATH: /dawarich_sqlite_data/dawarich_development_cache.sqlite3
-     CABLE_DATABASE_PATH: /dawarich_sqlite_data/dawarich_development_cable.sqlite3
...
    depends_on:
      dawarich_db:
        condition: service_healthy
        restart: true
+     dawarich_redis:
+       condition: service_healthy
+       restart: true
...
+ dawarich_sidekiq:
+   image: freikin/dawarich:latest
+   container_name: dawarich_sidekiq
+   volumes:
+     - dawarich_public:/var/app/public
+     - dawarich_watched:/var/app/tmp/imports/watched
+     - dawarich_storage:/var/app/storage
+   networks:
+     - dawarich
+   stdin_open: true
+   tty: true
+   entrypoint: sidekiq-entrypoint.sh
+   command: ['sidekiq']
+   restart: on-failure
+   environment:
+     RAILS_ENV: development
+     REDIS_URL: redis://dawarich_redis:6379
+     DATABASE_HOST: dawarich_db
+     DATABASE_USERNAME: postgres
+     DATABASE_PASSWORD: password
+     DATABASE_NAME: dawarich_development
+     APPLICATION_HOSTS: localhost
+     BACKGROUND_PROCESSING_CONCURRENCY: 10
+     APPLICATION_PROTOCOL: http
+     PROMETHEUS_EXPORTER_ENABLED: false
+     PROMETHEUS_EXPORTER_HOST: dawarich_app
+     PROMETHEUS_EXPORTER_PORT: 9394
+     SELF_HOSTED: "true"
+     STORE_GEODATA: "true"
+   logging:
+     driver: "json-file"
+     options:
+       max-size: "100m"
+       max-file: "5"
+   healthcheck:
+     test: [ "CMD-SHELL", "pgrep -f sidekiq" ]
+     interval: 10s
+     retries: 30
+     start_period: 30s
+     timeout: 10s
+   depends_on:
+     dawarich_db:
+       condition: service_healthy
+       restart: true
+     dawarich_redis:
+       condition: service_healthy
+       restart: true
+     dawarich_app:
+       condition: service_healthy
+       restart: true
...
volumes:
  dawarich_db_data:
- dawarich_sqlite_data:
  dawarich_shared:
  dawarich_public:
  dawarich_watched:
  dawarich_storage:
```

_I understand the confusion, probably even anger, caused by so many breaking changes in the recent days._

_I'm sorry._

### Fixed

- Fixed a bug where points from Immich and Photoprism did not have lonlat attribute set. #1318
- Added minimum password length to 6 characters. #1373
- Text size of countries being calculated is now smaller. #1371

### Changed

- Geocoder is now being installed from a private fork for debugging purposes.
- Redis is now being used for caching.
- Sidekiq is now being used for background jobs.

### Removed
- SolidQueue, SolidCache and SolidCable are now removed.


## [0.27.4] - 2025-06-06

⚠️ This release includes a breaking change. ⚠️

### Changed

- SolidQueue is now using PostgreSQL instead of SQLite. Provide `QUEUE_DATABASE_NAME`, `QUEUE_DATABASE_PASSWORD`, `QUEUE_DATABASE_USERNAME`, `QUEUE_DATABASE_PORT` and `QUEUE_DATABASE_HOST` environment variables to configure it. #1331
- SQLite databases are now being stored in the `dawarich_sqlite_data` volume. #1361 #1357

```diff
...
  dawarich_app:
    image: freikin/dawarich:latest
    container_name: dawarich_app
    volumes:
      - dawarich_public:/var/app/public
      - dawarich_watched:/var/app/tmp/imports/watched
      - dawarich_storage:/var/app/storage
      - dawarich_db_data:/dawarich_db_data
+     - dawarich_sqlite_data:/dawarich_sqlite_data
    ...
    restart: on-failure
    environment:
    ...
      DATABASE_NAME: dawarich_development
+     # PostgreSQL database name for solid_queue
+     QUEUE_DATABASE_NAME: dawarich_development_queue
+     QUEUE_DATABASE_PASSWORD: password
+     QUEUE_DATABASE_USERNAME: postgres
+     QUEUE_DATABASE_PORT: 5432
+     QUEUE_DATABASE_HOST: dawarich_db
      # SQLite database paths for cache and cable databases
-     QUEUE_DATABASE_PATH: /dawarich_db_data/dawarich_development_queue.sqlite3
-     CACHE_DATABASE_PATH: /dawarich_db_data/dawarich_development_cache.sqlite3
-     CABLE_DATABASE_PATH: /dawarich_db_data/dawarich_development_cable.sqlite3
+     CACHE_DATABASE_PATH: /dawarich_sqlite_data/dawarich_development_cache.sqlite3
+     CABLE_DATABASE_PATH: /dawarich_sqlite_data/dawarich_development_cable.sqlite3

volumes:
  dawarich_db_data:
+ dawarich_sqlite_data:
  dawarich_shared:
  dawarich_public:
  dawarich_watched:
  dawarich_storage:
...
```

## [0.27.3] - 2025-06-05

### Changed

- Added `PGSSENCMODE=disable` to the development environment to resolve sqlite3 error. #1326 #1331

### Fixed

- Fixed rake tasks to be run with `bundle exec`. #1320
- Fixed import name not being set when updating an import. #1269

### Added

- LocationIQ can now be used as a geocoding service. Set `LOCATIONIQ_API_KEY` to configure it. #1334


## [0.27.2] - 2025-06-02

You can now safely remove Redis and Sidekiq from your `docker-compose.yml` file, both containers, related volumes, environment variables and container dependencies.

```diff
services:
- dawarich_redis:
-   image: redis:7.0-alpine
-   container_name: dawarich_redis
-   command: redis-server
-   networks:
-     - dawarich
-   volumes:
-     - dawarich_shared:/data
-   restart: always
-   healthcheck:
-     test: [ "CMD", "redis-cli", "--raw", "incr", "ping" ]
-     interval: 10s
-     retries: 5
-     start_period: 30s
-     timeout: 10s
...
  dawarich_app:
    image: freikin/dawarich:latest
    environment:
      RAILS_ENV: development
-     REDIS_URL: redis://dawarich_redis:6379/0
...
    depends_on:
      dawarich_db:
        condition: service_healthy
        restart: true
-     dawarich_redis:
-       condition: service_healthy
-       restart: true
...
- dawarich_sidekiq:
-   image: freikin/dawarich:latest
-   container_name: dawarich_sidekiq
-   volumes:
-     - dawarich_public:/var/app/public
-     - dawarich_watched:/var/app/tmp/imports/watched
-     - dawarich_storage:/var/app/storage
-   networks:
-     - dawarich
-   stdin_open: true
-   tty: true
-   entrypoint: sidekiq-entrypoint.sh
-   command: ['sidekiq']
-   restart: on-failure
-   environment:
-     RAILS_ENV: development
-     REDIS_URL: redis://dawarich_redis:6379/0
-     DATABASE_HOST: dawarich_db
-     DATABASE_USERNAME: postgres
-     DATABASE_PASSWORD: password
-     DATABASE_NAME: dawarich_development
-     APPLICATION_HOSTS: localhost
-     BACKGROUND_PROCESSING_CONCURRENCY: 10
-     APPLICATION_PROTOCOL: http
-     PROMETHEUS_EXPORTER_ENABLED: false
-     PROMETHEUS_EXPORTER_HOST: dawarich_app
-     PROMETHEUS_EXPORTER_PORT: 9394
-     SELF_HOSTED: "true"
-     STORE_GEODATA: "true"
-   logging:
-     driver: "json-file"
-     options:
-       max-size: "100m"
-       max-file: "5"
-   healthcheck:
-     test: [ "CMD-SHELL", "bundle exec sidekiqmon processes | grep $${HOSTNAME}" ]
-     interval: 10s
-     retries: 30
-     start_period: 30s
-     timeout: 10s
-   depends_on:
-     dawarich_db:
-       condition: service_healthy
-       restart: true
-     dawarich_redis:
-       condition: service_healthy
-       restart: true
-     dawarich_app:
-       condition: service_healthy
-       restart: true
```

### Removed

- Redis and Sidekiq.



## [0.27.1] - 2025-06-01

### Fixed

- Cache jobs are now being scheduled correctly after app start.
- `countries.geojson` now have fixed alpha codes for France and Norway



## [0.27.0] - 2025-06-01

⚠️ This release includes a breaking change. ⚠️

Starting 0.27.0, Dawarich is using SolidQueue and SolidCache to run background jobs and cache data. Before updating, make sure your Sidekiq queues (https://your_dawarich_app/sidekiq) are empty.

Moving to SolidQueue and SolidCache will require creating new SQLite databases, which will be created automatically when you start the app. They will be stored in the `dawarich_db_data` volume.

Background jobs interface is now available at `/jobs` page.

Please, update your `docker-compose.yml` and add the following:

```diff
  dawarich_app:
    image: freikin/dawarich:latest
    container_name: dawarich_app
    volumes:
      - dawarich_public:/var/app/public
      - dawarich_watched:/var/app/tmp/imports/watched
      - dawarich_storage:/var/app/storage
+     - dawarich_db_data:/dawarich_db_data
...
    environment:
      ...
      DATABASE_NAME: dawarich_development
      # SQLite database paths for secondary databases
+     QUEUE_DATABASE_PATH: /dawarich_db_data/dawarich_development_queue.sqlite3
+     CACHE_DATABASE_PATH: /dawarich_db_data/dawarich_development_cache.sqlite3
+     CABLE_DATABASE_PATH: /dawarich_db_data/dawarich_development_cable.sqlite3
```


### Fixed

- Enable caching in development for the docker image to improve performance.

### Changed

- SolidCache is now being used for caching instead of Redis.
- SolidQueue is now being used for background jobs instead of Sidekiq.
- SolidCable is now being used as ActionCable adapter.
- Background jobs are now being run as Puma plugin instead of separate Docker container.
- The `rc` docker image is now being built for amd64 architecture only to speed up the build process.
- Deleting an import with many points now works significantly faster.



## [0.26.7] - 2025-05-29

### Fixed

- Popups now showing distance in the correct distance unit. #1258

### Added

- Bunch of system tests to cover map interactions.


## [0.26.6] - 2025-05-22

### Added

- armv8 to docker build. #1249

### Changed

- Points are now being created in the `points` queue. #1243
- Route opacity is now being displayed as percentage in the map settings. #462 #1224
- Exported GeoJSON file now contains coordinates as floats instead of strings, as per RFC 7946. #762
- Fog of war now can be set to 200 meter per point. #630
## [0.26.5] - 2025-05-20

### Fixed

- Wget is back to fix healthchecks. #1241 #1231
- Dockerfile.prod is now using slim image. #1245
- Dockerfiles now use jemalloc with check for architecture. #1235

## [0.26.4] - 2025-05-19

### Changed

- Docker image is now using slim image to introduce some memory optimizations.
- The trip page now looks a bit nicer.
- The "Yesterday" button on the map page was changed to "Today". #1215
- The "Create Import" button now disabled until files are uploaded.

## [0.26.3] - 2025-05-18

### Fixed

- Fixed a bug where default distance unit was not being set for users. #1206


## [0.26.2] - 2025-05-18

### Fixed

- Seeds are now working properly. #1207
- Fixed a bug where France flag was not being displayed correctly. #1204
- Fix blank map page caused by empty default distance unit. Default distance unit is now kilometers and can be changed in Settings -> Maps. #1206


## [0.26.1] - 2025-05-18

Geodata on demand

This release introduces a new environment variable `STORE_GEODATA` with default value `true` to control whether to store geodata in the database or not. Currently, geodata is being used when:

- Fetching places geodata
- Fetching countries for a trip
- Suggesting place name for a visit

Opting out of storing geodata will make each feature that uses geodata to make a direct request to the geocoding service to calculate required data instead of using existing geodata from the database. Setting `STORE_GEODATA` to `false` can also use you some database space.

If you decide to opt out, you can safely delete your existing geodata from the database:

1. Get into the [console](https://dawarich.app/docs/FAQ/#how-to-enter-dawarich-console)
2. Run the following commands:

```ruby
Point.update_all(geodata: {}) # to remove existing geodata

ActiveRecord::Base.connection.execute("VACUUM FULL") # to free up some space
```

Note, that this will take some time to complete, depending on the number of points you have. This is not a required step.

If you're running your own Photon instance, you can safely set `STORE_GEODATA` to `false`, otherwise it'd be better to keep it enabled, because that way Dawarich will be using existing geodata for its calculations.

Also, after updating to this version, Dawarich will start a huge background job to calculate countries for all your points. Just let it work.

### Added

- Map page now has a button to go to the previous and next day. #296 #631 #904
- Clicking on number of countries and cities in stats cards now opens a modal with a list of countries and cities visited in that year.

### Changed

- Reverse geocoding is now working as on-demand job instead of storing the result in the database. #619
- Stats cards now show the last update time. #733
- Visit card now shows buttons to confirm or decline a visit only if it's not confirmed or declined yet.
- Distance unit is now being stored in the user settings. You can choose between kilometers and miles, default is kilometers. The setting is accessible in the user settings -> Maps -> Distance Unit. You might want to recalculate your stats after changing the unit. #1126
- Fog of war is now being displayed as lines instead of dots. Thanks to @MeijiRestored!

### Fixed

- Fixed a bug with an attempt to write points with same lonlat and timestamp from iOS app. #1170
- Importing GeoJSON files now saves velocity if it was stored in either `velocity` or `speed` property.
- `bundle exec rake points:migrate_to_lonlat` should work properly now. #1083 #1161
- PostGIS extension is now being enabled only if it's not already enabled. #1186
- Fixed a bug where visits were returning into Suggested state after being confirmed or declined. #848
- If no points are found for a month during stats calculation, stats are now being deleted instead of being left empty. #1066 #406

### Removed

- Removed `DISTANCE_UNIT` constant. It can be safely removed from your environment variables in docker-compose.yml.


## [0.26.0] - 2025-05-08

⚠️ This release includes a breaking change. ⚠️

Starting this version, Dawarich requires PostgreSQL 17 with PostGIS 3.5. If you haven't updated your database image yet, please consider doing so as suggested in the [docs on the website](https://dawarich.app/docs/self-hosting/maintenance/update-postgresql). Simply replacing the image in the `docker-compose.yml` unfortunately doesn't work, as PostgreSQL 17 is not backwards compatible with 14 (which was used in previous versions).

If you have encountered problems with moving to a PostGIS image while still on Postgres 14, I collected a selection of compatible docker images for different CPU architectures, which you can also find in the [docs](https://dawarich.app/docs/self-hosting/maintenance/moving-to-postgis). New users will be automatically provisioned with PostgreSQL 17 with PostGIS 3.5 with default `docker-compose.yml` file.

**You still may use PostgreSQL 14, but no support will be provided for it starting this version. It's strongly recommended to update to PostgreSQL 17.**

### Changed

- Dawarich now uses PostgreSQL 17 with PostGIS 3.5 by default.


## [0.25.10] - 2025-05-08

### Added

- Vector maps are supported in non-self-hosted mode.
- Credentials for Sidekiq UI are now being set via environment variables: `SIDEKIQ_USERNAME` and `SIDEKIQ_PASSWORD`. Default credentials are `sidekiq` and `password`. If you don't set them, in self-hosted mode, Sidekiq UI will not be protected by basic auth.
- New import page now shows progress of the upload.

### Changed

- Datetime is now being displayed with seconds in the Points page. #1088
- Imported files are now being uploaded via direct uploads.
- `/api/v1/points` endpoint now creates accepted points synchronously.

### Removed

- Sample points are no longer being imported automatically for new users.

## [0.25.9] - 2025-04-29

### Fixed

- `bundle exec rake points:migrate_to_lonlat` task now works properly.

## [0.25.8] - 2025-04-24

### Fixed

- Database was not being created if it didn't exist. #1076

### Removed

- `RAILS_MASTER_KEY` environment variable is no longer being set. You can safely remove it from your environment variables.

## [0.25.7] - 2025-04-24

### Fixed

- Map loading error. #1094

## [0.25.6] - 2025-04-23

### Added

- In the map settings (top left corner of the map), you can now select colors for your colored routes. #682

### Changed

- Import edit page now allows to edit import name.
- Importing data now does not create a notification for the user.
- Updating stats now does not create a notification for the user.

### Fixed

- Fixed a bug where an import was failing due to partial file download. #1069 #1073 #1024 #1051

## [0.25.5] - 2025-04-18

This release introduces a new way to send transactional emails using SMTP. Example may include password reset, email confirmation, etc.

To enable SMTP mailing, you need to set the following environment variables:

- `SMTP_SERVER` - SMTP server address.
- `SMTP_PORT` - SMTP server port.
- `SMTP_DOMAIN` - SMTP server domain.
- `SMTP_USERNAME` - SMTP server username.
- `SMTP_PASSWORD` - SMTP server password.
- `SMTP_FROM` - Email address to send emails from.

This is optional feature and is not required for the app to work.

### Removed

- Optional telemetry was removed from the app. The `ENABLE_TELEMETRY` env var can be safely removed from docker compose.

### Changed

- `bundle exec rake points:migrate_to_lonlat` task now also tries to extract latitude and longitude from `raw_data` column before using `longitude` and `latitude` columns to fill `lonlat` column.
- Docker entrypoints are now using `DATABASE_NAME` environment variable to check if Postgres is existing/available.
- Sidekiq web UI is now protected by basic auth. Use `SIDEKIQ_USERNAME` and `SIDEKIQ_PASSWORD` environment variables to set the credentials.

### Added

- You can now provide SMTP settings in ENV vars to send emails.
- You can now edit imports. #1044 #623

### Fixed

- Importing data from Immich now works correctly. #1019


## [0.25.4] - 2025-04-02

⚠️ This release includes a breaking change. ⚠️

Make sure to add `dawarich_storage` volume and `SELF_HOSTED: "true"` to your `docker-compose.yml` file. Example:

```diff
...

  dawarich_app:
    image: freikin/dawarich:latest
    container_name: dawarich_app
    volumes:
      - dawarich_public:/var/app/public
      - dawarich_watched:/var/app/tmp/imports/watched
+     - dawarich_storage:/var/app/storage
...
    environment:
+     SELF_HOSTED: "true"

...

  dawarich_sidekiq:
    image: freikin/dawarich:latest
    container_name: dawarich_sidekiq
    volumes:
      - dawarich_public:/var/app/public
      - dawarich_watched:/var/app/tmp/imports/watched
+     - dawarich_storage:/var/app/storage
...
    environment:
+     SELF_HOSTED: "true"


volumes:
  dawarich_db_data:
  dawarich_shared:
  dawarich_public:
  dawarich_watched:
+ dawarich_storage:
```


In this release we're changing the way import files are being stored. Previously, they were being stored in the `raw_data` column of the `imports` table. Now, they are being attached to the import record. All new imports will be using the new storage, to migrate existing imports, you can use the `bundle exec rake imports:migrate_to_new_storage` task. Run it in the container shell.

This is an optional task, that will not affect your points or other data.
Big imports might take a while to migrate, so be patient.

Also, you can now migrate existing exports to the new storage using the `bundle exec rake exports:migrate_to_new_storage` task (in the container shell) or just delete them.

If your hardware doesn't have enough memory to migrate the imports, you can delete your imports and re-import them.

### Added

- Sentry is now can be used for error tracking.
- Subscription management is now available in non self-hosted mode.

### Changed

- Import files are now being attached to the import record instead of being stored in the `raw_data` database column.
- Import files can now be stored in S3-compatible storage.
- Export files are now being attached to the export record instead of being stored in the file system.
- Export files can now be stored in S3-compatible storage.
- Users can now import Google's Records.json file via the UI instead of using the CLI.
- Optional telemetry sending is now disabled and will be removed in the future.

### Fixed

- Moving points on the map now works correctly. #957
- `bundle exec rake points:migrate_to_lonlat` task now also reindexes the points table.
- Fixed filling `lonlat` column for old places after reverse geocoding.
- Deleting an import now correctly recalculates stats.
- Datetime across the app is now being displayed in human readable format, i.e 26 Dec 2024, 13:49. Hover over the datetime to see the ISO 8601 timestamp.


## [0.25.3] - 2025-03-22

### Fixed

- Fixed missing `bundle exec rake points:migrate_to_lonlat` task.

## [0.25.2] - 2025-03-21

### Fixed

- Migration to add unique index to points now contains code to remove duplicates from the database.
- Issue with ESRI maps not being displayed correctly. #956

### Added

- `bundle exec rake data_cleanup:remove_duplicate_points` task added to remove duplicate points from the database and export them to a CSV file.
- `bundle exec rake points:migrate_to_lonlat` task added for convenient manual migration of points to the new `lonlat` column.
- `bundle exec rake users:activate` task added to activate all users.

### Changed

- Merged visits now use the combined name of the merged visits.

## [0.25.1] - 2025-03-17

### Fixed

- Coordinates on the Points page are now being displayed correctly.

## [0.25.0] - 2025-03-09

This release is focused on improving the visits experience.

Since previous implementation of visits was not working as expected, this release introduces a new approach. It is recommended to remove all _non-confirmed_ visits before or after updating to this version.

There is a known issue when data migrations are not being run automatically on some systems. If you're experiencing issues when opening map page, trips page or when trying to see visits, try executing the following command in the [Console](https://dawarich.app/docs/FAQ/#how-to-enter-dawarich-console):

```ruby
User.includes(:tracked_points, visits: :places).find_each do |user|
  places_to_update = user.places.where(lonlat: nil)

  # For each place, set the lonlat value based on longitude and latitude
  places_to_update.find_each do |place|
    next if place.longitude.nil? || place.latitude.nil?

    # Set the lonlat to a PostGIS point with the proper SRID
    # rubocop:disable Rails/SkipsModelValidations
    place.update_column(:lonlat, "SRID=4326;POINT(#{place.longitude} #{place.latitude})")
    # rubocop:enable Rails/SkipsModelValidations
  end

  user.tracked_points.update_all('lonlat = ST_SetSRID(ST_MakePoint(longitude, latitude), 4326)')
end
```

With any errors, don't hesitate to ask for help in the [Discord server](https://discord.gg/pHsBjpt5J8).

### Added

- A new button to open the visits drawer.
- User can now confirm or decline visits directly from the visits drawer.
- Visits are now being shown on the map: orange circles for suggested visits and slightly bigger blue circles for confirmed visits.
- User can click on a visit circle to rename it and select a place for it.
- User can click on a visit card in the drawer panel to move to it on the map.
- User can select click on the "Select area" button in the top right corner of the map to select an area on the map. Once area is selected, visits for all times in that area will be shown on the map, regardless of whether they are in the selected time range or not.
- User can now select two or more visits in the visits drawer and merge them into a single visit. This operation is not reversible.
- User can now select two or more visits in the visits drawer and confirm or decline them at once. This operation is not reversible.
- Status field to the User model. Inactive users are now being restricted from accessing some of the functionality, which is mostly about writing data to the database. Reading is remaining unrestricted.
- After user is created, a sample import is being created for them to demonstrate how to use the app.


### Changed

- Links to Points, Visits & Places, Imports and Exports were moved under "My data" section in the navbar.
- Restrict access to Sidekiq in non self-hosted mode.
- Restrict access to background jobs in non self-hosted mode.
- Restrict access to users management in non self-hosted mode.
- Restrict access to API for inactive users.
- All users in self-hosted mode are active by default.
- Points are now using `lonlat` column for storing longitude and latitude.
- Semantic history points are now being imported much faster.
- GPX files are now being imported much faster.
- Trips, places and points are now using PostGIS' database attributes for storing longitude and latitude.
- Distance calculation are now using Postgis functions and expected to be more accurate.

### Fixed

- Fixed a bug where non-admin users could not import Immich and Photoprism geolocation data.
- Fixed a bug where upon point deletion it was not being removed from the map, while it was actually deleted from the database. #883
- Fixed a bug where upon import deletion stats were not being recalculated. #824

## [0.24.1] - 2025-02-13

Custom map tiles

In the user settings, you can now set a custom tile URL for the map. This is useful if you want to use a custom map tile provider or if you want to use a map tile provider that is not listed in the dropdown.

To set a custom tile URL, go to the user settings and set the `Maps` section to your liking. Be mindful that currently, only raster tiles are supported. The URL should be a valid tile URL, like `https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png`. You, as the user, are responsible for any extra costs that may occur due to using a custom tile URL.

### Added

- Safe settings for user with default values.
- Nominatim API is now supported as a reverse geocoding provider.
- In the user settings, you can now set a custom tile URL for the map. #429 #715
- In the user map settings, you can now see a chart of map tiles usage.
- If you have Prometheus exporter enabled, you can now see a `ruby_dawarich_map_tiles` metric in Prometheus, which shows the total number of map tiles loaded. Example:

```
# HELP ruby_dawarich_map_tiles_usage
# TYPE ruby_dawarich_map_tiles_usage counter
ruby_dawarich_map_tiles_usage 99
```

### Fixed

- Speed on the Points page is now being displayed in kilometers per hour. #700
- Fog of war displacement #774

### Reverted

- #748

## [0.24.0] - 2025-02-10

Points speed units

Dawarich expects speed to be sent in meters per second. It's already known that OwnTracks and GPSLogger (in some configurations) are sending speed in kilometers per hour.

In GPSLogger it's easily fixable: if you previously had `"vel": "%SPD_KMH"`, change it to `"vel": "%SPD"`, like it's described in the [docs](https://dawarich.app/docs/tutorials/track-your-location#gps-logger).

In OwnTracks it's a bit more complicated. You can't change the speed unit in the settings, so Dawarich will expect speed in kilometers per hour and will convert it to meters per second. Nothing is needed to be done from your side.

Now, we need to fix existing points with speed in kilometers per hour. The following guide assumes that you have been tracking your location exclusively with speed in kilometers per hour. If you have been using both speed units (say, were tracking with OwnTracks in kilometers per hour and with GPSLogger in meters per second), you need to decide what to do with points that have speed in kilometers per hour, as there is no easy way to distinguish them from points with speed in meters per second.

To convert speed in kilometers per hour to meters per second in your points, follow these steps:

1. Enter [Dawarich console](https://dawarich.app/docs/FAQ#how-to-enter-dawarich-console)
2. Run `points = Point.where(import_id: nil).where.not(velocity: [nil, "0"]).where("velocity NOT LIKE '%.%'")`. This will return all tracked (not imported) points.
3. Run
```ruby
points.update_all("velocity = CAST(ROUND(CAST((CAST(velocity AS FLOAT) * 1000 / 3600) AS NUMERIC), 1) AS TEXT)")

```

This will convert speed in kilometers per hour to meters per second and round it to 1 decimal place.

If you have been using both speed units, but you know the dates where you were tracking with speed in kilometers per hour, on the second step of the instruction above, you can add `where("timestamp BETWEEN ? AND ?", Date.parse("2025-01-01").beginning_of_day.to_i, Date.parse("2025-01-31").end_of_day.to_i)` to the query to convert speed in kilometers per hour to meters per second only for a specific period of time. Resulting query will look like this:

```ruby
start_at = DateTime.new(2025, 1, 1, 0, 0, 0).in_time_zone(Time.current.time_zone).to_i
end_at = DateTime.new(2025, 1, 31, 23, 59, 59).in_time_zone(Time.current.time_zone).to_i
points = Point.where(import_id: nil).where.not(velocity: [nil, "0"]).where("timestamp BETWEEN ? AND ?", start_at, end_at).where("velocity NOT LIKE '%.%'")
```

This will select points tracked between January 1st and January 31st 2025. Then just use step 3 to convert speed in kilometers per hour to meters per second.

### Changed

- Speed for points, that are sent to Dawarich via `POST /api/v1/owntracks/points` endpoint, will now be converted to meters per second, if `topic` param is sent. The official GPSLogger instructions are assuming user won't be sending `topic` param, so this shouldn't affect you if you're using GPSLogger.

### Fixed

- After deleting one point from the map, other points can now be deleted as well. #723 #678
- Fixed a bug where export file was not being deleted from the server after it was deleted. #808
- After an area was drawn on the map, a popup is now being shown to allow user to provide a name and save the area. #740
- Docker entrypoints now use database name to fix problem with custom database names.
- Garmin GPX files with empty tracks are now being imported correctly. #827

### Added

- `X-Dawarich-Version` header to the `GET /api/v1/health` endpoint response.

## [0.23.6] - 2025-02-06

### Added

- Enabled Postgis extension for PostgreSQL.
- Trips are now store their paths in the database independently of the points.
- Trips are now being rendered on the map using their precalculated paths instead of list of coordinates.

### Changed

- Ruby version was updated to 3.4.1.
- Requesting photos on the Map page now uses the start and end dates from the URL params. #589

## [0.23.5] - 2025-01-22

### Added

- A test for building rc Docker image.

### Fixed

- Fix authentication to `GET /api/v1/countries/visited_cities` with header `Authorization: Bearer YOUR_API_KEY` instead of `api_key` query param. #679
- Fix a bug where a gpx file with empty tracks was not being imported. #646
- Fix a bug where rc version was being checked as a stable release. #711

## [0.23.3] - 2025-01-21

### Changed

- Synology-related files are now up to date. #684

### Fixed

- Drastically improved performance for Google's Records.json import. It will now take less than 5 minutes to import 500,000 points, which previously took a few hours.

### Fixed

- Add index only if it doesn't exist.

## [0.23.1] - 2025-01-21

### Fixed

- Renamed unique index on points to `unique_points_lat_long_timestamp_user_id_index` to fix naming conflict with `unique_points_index`.

## [0.23.0] - 2025-01-20

⚠️ IMPORTANT ⚠️

This release includes a data migration to remove duplicated points from the database. It will not remove anything except for duplcates from the `points` table, but please make sure to create a [backup](https://dawarich.app/docs/tutorials/backup-and-restore) before updating to this version.

### Added

- `POST /api/v1/points/create` endpoint added.
- An index to guarantee uniqueness of points across `latitude`, `longitude`, `timestamp` and `user_id` values. This is introduced to make sure no duplicates will be created in the database in addition to previously existing validations.
- `GET /api/v1/users/me` endpoint added to get current user.

## [0.22.4] - 2025-01-20

### Added

- You can now drag-n-drop a point on the map to update its position. Enable the "Points" layer on the map to see the points.
- `PATCH /api/v1/points/:id` endpoint added to update a point. It only accepts `latitude` and `longitude` params. #51 #503

### Changed

- Run seeds even in prod env so Unraid users could have default user.
- Precompile assets in production env using dummy secret key base.

### Fixed

- Fixed a bug where route wasn't highlighted when it was hovered or clicked.

## [0.22.3] - 2025-01-14

### Changed

- The Map now uses a canvas to draw polylines, points and fog of war. This should improve performance in browser with a lot of points and polylines.

## [0.22.2] - 2025-01-13

✨ The Fancy Routes release ✨

### Added

- In the Map Settings (coggle in the top left corner of the map), you can now enable/disable the Fancy Routes feature. Simply said, it will color your routes based on the speed of each segment.
- Hovering over a polyline now shows the speed of the segment. Move cursor over a polyline to see the speed of different segments.
- Distance and points number in the custom control to the map.

### Changed

- The name of the "Polylines" feature is now "Routes".

⚠️ Important note on the Prometheus monitoring ⚠️

In the previous release, `bin/dev` command in the default `docker-compose.yml` file was replaced with `bin/rails server -p 3000 -b ::`, but this way Dawarich won't be able to start Prometheus Exporter. If you want to use Prometheus monitoring, you need to use `bin/dev` command instead.

Example:

```diff
  dawarich_app:
    image: freikin/dawarich:latest
...
-    command: ['bin/rails', 'server', '-p', '3000', '-b', '::']
+    command: ['bin/dev']
```

## [0.22.1] - 2025-01-09

### Removed

- Gems caching volume from the `docker-compose.yml` file.

To update existing `docker-compose.yml` to new changes, refer to the following:

```diff
  dawarich_app:
    image: freikin/dawarich:latest
...
    volumes:
-      - dawarich_gem_cache_app:/usr/local/bundle/gems
...
  dawarich_sidekiq:
    image: freikin/dawarich:latest
...
    volumes:
-      - dawarich_gem_cache_app:/usr/local/bundle/gems
...

volumes:
  dawarich_db_data:
- dawarich_gem_cache_app:
- dawarich_gem_cache_sidekiq:
  dawarich_shared:
  dawarich_public:
  dawarich_watched:
```

### Changed

- `GET /api/v1/health` endpoint now returns a `X-Dawarich-Response: Hey, Im alive and authenticated!` header if user is authenticated.

## [0.22.0] - 2025-01-09

⚠️ This release introduces a breaking change. ⚠️

Please read this release notes carefully before upgrading.

Docker-related files were moved to the `docker` directory and some of them were renamed. Before upgrading, study carefully changes in the `docker/docker-compose.yml` file and update your docker-compose file accordingly, so it uses the new files and commands. Copying `docker/docker-compose.yml` blindly may lead to errors.

No volumes were removed or renamed, so with a proper docker-compose file, you should be able to upgrade without any issues.

To update existing `docker-compose.yml` to new changes, refer to the following:

```diff
  dawarich_app:
    image: freikin/dawarich:latest
...
-    entrypoint: dev-entrypoint.sh
-    command: ['bin/dev']
+    entrypoint: web-entrypoint.sh
+    command: ['bin/rails', 'server', '-p', '3000', '-b', '::']
...
  dawarich_sidekiq:
    image: freikin/dawarich:latest
...
-    entrypoint: dev-entrypoint.sh
-    command: ['bin/dev']
+    entrypoint: sidekiq-entrypoint.sh
+    command: ['bundle', 'exec', 'sidekiq']
```

Although `docker-compose.production.yml` was added, it's not being used by default. It's just an example of how to configure Dawarich for production. The default `docker-compose.yml` file is still recommended for running the app.

### Changed

- All docker-related files were moved to the `docker` directory.
- Default memory limit for `dawarich_app` and `dawarich_sidekiq` services was increased to 4GB.
- `dawarich_app` and `dawarich_sidekiq` services now use separate entrypoint scripts.
- Gems (dependency libraries) are now being shipped as part of the Dawarich Docker image.

### Fixed

- Visit suggesting job does nothing if user has no tracked points.
- `BulkStatsCalculationJob` now being called without arguments in the data migration.

### Added

- A proper production Dockerfile, docker-compose and env files.

## [0.21.6] - 2025-01-07

### Changed

- Disabled visit suggesting job after import.
- Improved performance of the `User#years_tracked` method.

### Fixed

- Inconsistent password for the `dawarich_db` service in `docker-compose_mounted_volumes.yml`. #605
- Points are now being rendered with higher z-index than polylines. #577
- Run cache cleaning and preheating jobs only on server start. #594

## [0.21.5] - 2025-01-07

You may now use Geoapify API for reverse geocoding. To obtain an API key, sign up at https://myprojects.geoapify.com/ and create a new project. Make sure you have read and understood the [pricing policy](https://www.geoapify.com/pricing) and [Terms and Conditions](https://www.geoapify.com/terms-and-conditions/).

### Added

- Geoapify API support for reverse geocoding. Provide `GEOAPIFY_API_KEY` env var to use it.

### Removed

- Photon ENV vars from the `.env.development` and docker-compose.yml files.
- `APPLICATION_HOST` env var.
- `REVERSE_GEOCODING_ENABLED` env var.

## [0.21.4] - 2025-01-05

### Fixed

- Fixed a bug where Photon API for patreon supporters was not being used for reverse geocoding.

## [0.21.3] - 2025-01-04

### Added

- A notification about Photon API being under heavy load.

### Removed

- The notification about telemetry being enabled.

### Reverted

- ~~Imported points will now be reverse geocoded only after import is finished.~~

## [0.21.2] - 2024-12-25

### Added

- Logging for Immich responses.
- Watcher now supports all data formats that can be imported via web interface.

### Changed

- Imported points will now be reverse geocoded only after import is finished.

### Fixed

- Markers on the map are now being rendered with higher z-index than polylines. #577

## [0.21.1] - 2024-12-24

### Added

- Cache cleaning and preheating upon application start.
- `PHOTON_API_KEY` env var to set Photon API key. It's an optional env var, but it's required if you want to use Photon API as a Patreon supporter.
- 'X-Dawarich-Response' header to the `GET /api/v1/health` endpoint. It's set to 'Hey, I\'m alive!' to make it easier to check if the API is working.

### Changed

- Custom config for PostgreSQL is now optional in `docker-compose.yml`.

## [0.21.0] - 2024-12-20

⚠️ This release introduces a breaking change. ⚠️

The `dawarich_db` service now uses a custom `postgresql.conf` file.

As @tabacha pointed out in #549, the default `shm_size` for the `dawarich_db` service is too small and it may lead to database performance issues. This release introduces a `shm_size` parameter to the `dawarich_db` service to increase the size of the shared memory for PostgreSQL. This should help database with peforming vacuum and other operations. Also, it introduces a custom `postgresql.conf` file to the `dawarich_db` service.

To mount a custom `postgresql.conf` file, you need to create a `postgresql.conf` file in the `dawarich_db` service directory and add the following line to it:

```diff
  dawarich_db:
    image: postgis/postgis:14-3.5-alpine
    shm_size: 1G
    container_name: dawarich_db
    volumes:
      - dawarich_db_data:/var/lib/postgresql/data
      - dawarich_shared:/var/shared
+     - ./postgresql.conf:/etc/postgresql/postgres.conf # Provide path to custom config
  ...
    healthcheck:
      test: [ "CMD-SHELL", "pg_isready -U postgres -d dawarich_development" ]
      interval: 10s
      retries: 5
      start_period: 30s
      timeout: 10s
+   command: postgres -c config_file=/etc/postgresql/postgres.conf # Use custom config
```

To ensure your database is using custom config, you can connect to the container (`docker exec -it dawarich_db psql -U postgres`) and run `SHOW config_file;` command. It should return the following path: `/etc/postgresql/postgresql.conf`.

An example of a custom `postgresql.conf` file is provided in the `postgresql.conf.example` file.

### Added

- A button on a year stats card to update stats for the whole year. #466
- A button on a month stats card to update stats for a specific month. #466
- A confirmation alert on the Notifications page before deleting all notifications.
- A `shm_size` parameter to the `dawarich_db` service to increase the size of the shared memory for PostgreSQL. This should help database with peforming vacuum and other operations.

```diff
  ...
  dawarich_db:
    image: postgis/postgis:14-3.5-alpine
+   shm_size: 1G
  ...
```

- In addition to `api_key` parameter, `Authorization` header is now being used to authenticate API requests. #543

Example:

```
Authorization: Bearer YOUR_API_KEY
```

### Changed

- The map borders were expanded to make it easier to scroll around the map for New Zealanders.
- The `dawarich_db` service now uses a custom `postgresql.conf` file.
- The popup over polylines now shows dates in the user's format, based on their browser settings.

## [0.20.2] - 2024-12-17

### Added

- A point id is now being shown in the point popup.

### Fixed

- North Macedonia is now being shown on the scratch map. #537

### Changed

- The app process is now bound to :: instead of 0.0.0.0 to provide compatibility with IPV6.
- The app was updated to use Rails 8.0.1.

## [0.20.1] - 2024-12-16

### Fixed

- Setting `reverse_geocoded_at` for points that don't have geodata is now being performed in background job, in batches of 10,000 points to prevent memory exhaustion and long-running data migration.

## [0.20.0] - 2024-12-16

### Added

- `GET /api/v1/points/tracked_months` endpoint added to get list of tracked years and months.
- `GET /api/v1/countries/visited_cities` endpoint added to get list of visited cities.
- A link to the docs leading to a help chart for k8s. #550
- A button to delete all notifications. #548
- A support for `RAILS_LOG_LEVEL` env var to change log level. More on that here: https://guides.rubyonrails.org/debugging_rails_applications.html#log-levels. The available log levels are: `:debug`, `:info`, `:warn`, `:error`, `:fatal`, and `:unknown`, corresponding to the log level numbers from 0 up to 5, respectively. The default log level is `:debug`. #540
- A devcontainer to improve developers experience. #546

### Fixed

- A point popup is no longer closes when hovering over a polyline. #536
- When polylines layer is disabled and user deletes a point from its popup, polylines layer is no longer being enabled right away. #552
- Paths to gems within the sidekiq and app containers. #499

### Changed

- Months and years navigation is moved to a map panel on the right side of the map.
- List of visited cities is now being shown in a map panel on the right side of the map.

## [0.19.7] - 2024-12-11

### Fixed

- Fixed a bug where upon deleting a point on the map, the confirmation dialog was shown multiple times and the point was not being deleted from the map until the page was reloaded. #435

### Changed

- With the "Points" layer enabled on the map, points with negative speed are now being shown in orange color. Since Overland reports negative speed for points that might be faulty, this should help you to identify them.
- On the Points page, speed of the points with negative speed is now being shown in red color.

## [0.19.6] - 2024-12-11

⚠️ This release introduces a breaking change. ⚠️

The `dawarich_shared` volume now being mounted to `/data` instead of `/var/shared` within the container. It fixes Redis data being lost on container restart.

To change this, you need to update the `docker-compose.yml` file:

```diff
  dawarich_redis:
    image: redis:7.0-alpine
    container_name: dawarich_redis
    command: redis-server
    volumes:
+     - dawarich_shared:/data
    restart: always
    healthcheck:
```

Telemetry is now disabled by default. To enable it, you need to set `ENABLE_TELEMETRY` env var to `true`. For those who have telemetry enabled using `DISABLE_TELEMETRY` env var set to `false`, telemetry is now disabled by default.

### Fixed

- Flash messages are now being removed after 5 seconds.
- Fixed broken migration that was preventing the app from starting.
- Visits page is now loading a lot faster than before.
- Redis data should now be preserved on container restart.
- Fixed a bug where export files could have double extension, e.g. `file.gpx.gpx`.

### Changed

- Places page is now accessible from the Visits & Places tab on the navbar.
- Exporting process is now being logged.
- `ENABLE_TELEMETRY` env var is now used instead of `DISABLE_TELEMETRY` to enable/disable telemetry.

## [0.19.5] - 2024-12-10

### Fixed

- Fixed a bug where the map and visits pages were throwing an error due to incorrect approach to distance calculation.

## [0.19.4] - 2024-12-10

⚠️ This release introduces a breaking change. ⚠️

The `GET /api/v1/trips/:id/photos` endpoint now returns a different structure of the response:

```diff
{
  id: 1,
  latitude: 10,
  longitude: 10,
  localDateTime: "2024-01-01T00:00:00Z",
  originalFileName: "photo.jpg",
  city: "Berlin",
  state: "Berlin",
  country: "Germany",
  type: "image",
+ orientation: "portrait",
  source: "photoprism"
}
```

### Fixed

- Fixed a bug where the Photoprism photos were not being shown on the trip page.
- Fixed a bug where the Immich photos were not being shown on the trip page.
- Fixed a bug where the route popup was showing distance in kilometers instead of miles. #490

### Added

- A link to the Photoprism photos on the trip page if there are any.
- A `orientation` field in the Api::PhotoSerializer, hence the `GET /api/v1/photos` endpoint now includes the orientation of the photo. Valid values are `portrait` and `landscape`.
- Examples for the `type`, `orientation` and `source` fields in the `GET /api/v1/photos` endpoint in the Swagger UI.
- `DISABLE_TELEMETRY` env var to disable telemetry. More on telemetry: https://dawarich.app/docs/tutorials/telemetry
- `reverse_geocoded_at` column added to the `points` table.

### Changed

- On the Stats page, the "Reverse geocoding" section is now showing the number of points that were reverse geocoded based on `reverse_geocoded_at` column, value of which is based on the time when the point was reverse geocoded. If no geodata for the point is available, `reverse_geocoded_at` will be set anyway. Number of points that were reverse geocoded but no geodata is available for them is shown below the "Reverse geocoded" number.


## [0.19.3] - 2024-12-06

### Changed

- Refactored stats calculation to calculate only necessary stats, instead of calculating all stats
- Stats are now being calculated every 1 hour instead of 6 hours
- List of years on the Map page is now being calculated based on user's points instead of stats. It's also being cached for 1 day due to the fact that it's usually a heavy operation based on the number of points.
- Reverse-geocoding points is now being performed in batches of 1,000 points to prevent memory exhaustion.

### Added

- In-app notification about telemetry being enabled.

## [0.19.2] - 2024-12-04

The Telemetry release

Dawarich now can collect usage metrics and send them to InfluxDB. Before this release, the only metrics that could be somehow tracked by developers (only @Freika, as of now) were the number of stars on GitHub and the overall number of docker images being pulled, across all versions of Dawarich, non-splittable by version. New in-app telemetry will allow us to track more granular metrics, allowing me to make decisions based on facts, not just guesses.

I'm aware about the privacy concerns, so I want to be very transparent about what data is being sent and how it's used.

Data being sent:

- Number of DAU (Daily Active Users)
- App version
- Instance ID (unique identifier of the Dawarich instance built by hashing the api key of the first user in the database)

The data is being sent to a InfluxDB instance hosted by me and won't be shared with anyone.

Basically this set of metrics allows me to see how many people are using Dawarich and what versions they are using. No other data is being sent, nor it gives me any knowledge about individual users or their data or activity.

The telemetry is enabled by default, but it **can be disabled** by setting `DISABLE_TELEMETRY` env var to `true`. The dataset might change in the future, but any changes will be documented here in the changelog and in every release as well as on the [telemetry page](https://dawarich.app/docs/tutorials/telemetry) of the website docs.

### Added

- Telemetry feature. It's now collecting usage metrics and sending them to InfluxDB.

## [0.19.1] - 2024-12-04

### Fixed

- Sidekiq is now being correctly exported to Prometheus with `PROMETHEUS_EXPORTER_ENABLED=true` env var in `dawarich_sidekiq` service.

## [0.19.0] - 2024-12-04

The Photoprism integration release

⚠️ This release introduces a breaking change. ⚠️
The `GET /api/v1/photos` endpoint now returns following structure of the response:

```json
[
  {
    "id": "1",
    "latitude": 11.22,
    "longitude": 12.33,
    "localDateTime": "2024-01-01T00:00:00Z",
    "originalFileName": "photo.jpg",
    "city": "Berlin",
    "state": "Berlin",
    "country": "Germany",
    "type": "image", // "image" or "video"
    "source": "photoprism" // "photoprism" or "immich"
  }
]
```

### Added

- Photos from Photoprism are now can be shown on the map. To enable this feature, you need to provide your Photoprism instance URL and API key in the Settings page. Then you need to enable "Photos" layer on the map (top right corner).
- Geodata is now can be imported from Photoprism to Dawarich. The "Import Photoprism data" button on the Imports page will start the import process.

### Fixed

- z-index on maps so they won't overlay notifications dropdown
- Redis connectivity where it's not required

## [0.18.2] - 2024-11-29

### Added

- Demo account. You can now login with `demo@dawarich.app` / `password` to see how Dawarich works. This replaces previous default credentials.

### Changed

- The login page now shows demo account credentials if `DEMO_ENV` env var is set to `true`.

## [0.18.1] - 2024-11-29

### Fixed

- Fixed a bug where the trips interface was breaking when Immich integration is not configured.

### Added

- Flash messages are now being shown on the map when Immich integration is not configured.

## [0.18.0] - 2024-11-28

The Trips release

You can now create, edit and delete trips. To create a trip, click on the "New Trip" button on the Trips page. Provide a name, date and time for start and end of the trip. You can add your own notes to the trip as well.

If you have points tracked during provided timeframe, they will be automatically added to the trip and will be shown on the trip map.

Also, if you have Immich integrated, you will see photos from the trip on the trip page, along with a link to look at them on Immich.

### Added

- The Trips feature. Read above for more details.

### Changed

- Maps are now not so rough on the edges.

## [0.17.2] - 2024-11-27

### Fixed

- Retrieving photos from Immich now using `takenAfter` and `takenBefore` instead of `createdAfter` and `createdBefore`. With `createdAfter` and `createdBefore` Immich was returning no items some years.

## [0.17.1] - 2024-11-27

### Fixed

- Retrieving photos from Immich now correctly handles cases when Immich returns no items. It also logs the response from Immich for debugging purposes.

## [0.17.0] - 2024-11-26

The Immich Photos release

With this release, Dawarich can now show photos from your Immich instance on the map.

To enable this feature, you need to provide your Immich instance URL and API key in the Settings page. Then you need to enable "Photos" layer on the map (top right corner).

An important note to add here is that photos are heavy and hence generate a lot of traffic. The response from Immich for specific dates is being cached in Redis for 1 day, and that may lead to Redis taking a lot more space than previously. But since the cache is being expired after 24 hours, you'll get your space back pretty soon.

The other thing worth mentioning is how Dawarich gets data from Immich. It goes like this:

1. When you click on the "Photos" layer, Dawarich will make a request to `GET /api/v1/photos` endpoint to get photos for the selected timeframe.
2. This endpoint will make a request to `POST /search/metadata` endpoint of your Immich instance to get photos for the selected timeframe.
3. The response from Immich is being cached in Redis for 1 day.
4. Dawarich's frontend will make a request to `GET /api/v1/photos/:id/thumbnail.jpg` endpoint to get photo thumbnail from Immich. The number of requests to this endpoint will depend on how many photos you have in the selected timeframe.
5. For each photo, Dawarich's frontend will make a request to `GET /api/v1/photos/:id/thumbnail.jpg` endpoint to get photo thumbnail from Immich. This thumbnail request is also cached in Redis for 1 day.


### Added

- If you have provided your Immich instance URL and API key, the map will now show photos from your Immich instance when Photos layer is enabled.
- `GET /api/v1/photos` endpoint added to get photos from Immich.
- `GET /api/v1/photos/:id/thumbnail.jpg` endpoint added to get photo thumbnail from Immich.

## [0.16.9] - 2024-11-24

### Changed

- Rate limit for the Photon API is now 1 request per second. If you host your own Photon API instance, reverse geocoding requests will not be limited.
- Requests to the Photon API are now have User-Agent header set to "Dawarich #{APP_VERSION} (https://dawarich.app)"

## [0.16.8] - 2024-11-20

### Changed

- Default number of Puma workers is now 2 instead of 1. This should improve the performance of the application. If you have a lot of users, you might want to increase the number of workers. You can do this by setting the `WEB_CONCURRENCY` env var in your `docker-compose.yml` file. Example:

```diff
  dawarich_app:
    image: freikin/dawarich:latest
    container_name: dawarich_app
    environment:
      ...
      WEB_CONCURRENCY: "2"
```

## [0.16.7] - 2024-11-20

### Changed

- Prometheus exporter is now bound to 0.0.0.0 instead of localhost
- `PROMETHEUS_EXPORTER_HOST` and `PROMETHEUS_EXPORTER_PORT` env vars were added to the `docker-compose.yml` file to allow you to set the host and port for the Prometheus exporter. They should be added to both `dawarich_app` and `dawarich_sidekiq` services Example:

```diff
  dawarich_app:
    image: freikin/dawarich:latest
    container_name: dawarich_app
    environment:
      ...
      PROMETHEUS_EXPORTER_ENABLED: "true"
+     PROMETHEUS_EXPORTER_HOST: 0.0.0.0
+     PROMETHEUS_EXPORTER_PORT: "9394"

  dawarich_sidekiq:
    image: freikin/dawarich:latest
    container_name: dawarich_sidekiq
    environment:
      ...
      PROMETHEUS_EXPORTER_ENABLED: "true"
+     PROMETHEUS_EXPORTER_HOST: dawarich_app
+     PROMETHEUS_EXPORTER_PORT: "9394"
```

## [0.16.6] - 2024-11-20

### Added

- Dawarich now can export metrics to Prometheus. You can find the metrics at `your.host:9394/metrics` endpoint. The metrics are being exported in the Prometheus format and can be scraped by Prometheus server. To enable exporting, set the `PROMETHEUS_EXPORTER_ENABLED` env var in your docker-compose.yml to `true`. Example:

```yaml
  dawarich_app:
    image: freikin/dawarich:latest
    container_name: dawarich_app
    environment:
      ...
      PROMETHEUS_EXPORTER_ENABLED: "true"
```

## [0.16.5] - 2024-11-18

### Changed

- Dawarich now uses `POST /api/search/metadata` endpoint to get geodata from Immich.

## [0.16.4] - 2024-11-12

### Added

- Admins can now see all users in the system on the Users page. The path is `/settings/users`.

### Changed

- Admins can now provide custom password for new users and update passwords for existing users on the Users page.
- The `bin/dev` file will no longer run `bin/rails tailwindcss:watch` command. It's useful only for development and doesn't really make sense to run it in production.

### Fixed

- Exported files will now always have an extension when downloaded. Previously, the extension was missing in case of GPX export.
- Deleting and sorting points on the Points page will now preserve filtering and sorting params when points are deleted or sorted. Previously, the page was being reloaded and filtering and sorting params were lost.

## [0.16.3] - 2024-11-10

### Fixed

- Make ActionCable respect REDIS_URL env var. Previously, ActionCable was trying to connect to Redis on localhost.

## [0.16.2] - 2024-11-08

### Fixed

- Exported GPX file now being correctly recognized as valid by Garmin Connect, Adobe Lightroom and (probably) other services. Previously, the exported GPX file was not being recognized as valid by these services.

## [0.16.1] - 2024-11-08

### Fixed

- Speed is now being recorded into points when a GPX file is being imported. Previously, the speed was not being recorded.
- GeoJSON file from GPSLogger now can be imported to Dawarich. Previously, the import was failing due to incorrect parsing of the file.

### Changed

- The Vists suggestion job is disabled. It will be re-enabled in the future with a new approach to the visit suggestion process.

## [0.16.0] - 2024-11-07

The Websockets release

### Added

- New notifications are now being indicated with a blue-ish dot in the top right corner of the screen. Hovering over the bell icon will show you last 10 notifications.
- New points on the map will now be shown in real-time. No need to reload the map to see new points.
- User can now enable or disable Live Mode in the map controls. When Live Mode is enabled, the map will automatically scroll to the new points as they are being added to the map.

### Changed

- Scale on the map now shows the distance both in kilometers and miles.

## [0.15.13] - 2024-11-01

### Added

- `GET /api/v1/countries/borders` endpoint to get countries for scratch map feature

## [0.15.12] - 2024-11-01

### Added

- Scratch map. You can enable it in the map controls. The scratch map highlight countries you've visited. The scratch map is working properly only if you have your points reverse geocoded.

## [0.15.11] - 2024-10-29

### Added

- Importing Immich data on the Imports page now will trigger an attempt to write raw json file with the data from Immich to `tmp/imports/immich_raw_data_CURRENT_TIME_USER_EMAIL.json` file. This is useful to debug the problem with the import if it fails. #270

### Fixed

- New app version is now being checked every 6 hours instead of 1 day and the check is being performed in the background. #238

### Changed

- ⚠️ The instruction to import `Records.json` from Google Takeout now mentions `tmp/imports` directory instead of `public/imports`. ⚠️ #326
- Hostname definition for Sidekiq healtcheck to solve #344. See the diff:

```diff
  dawarich_sidekiq:
    image: freikin/dawarich:latest
    container_name: dawarich_sidekiq
    healthcheck:
-     test: [ "CMD-SHELL", "bundle exec sidekiqmon processes | grep $(hostname)" ]
+     test: [ "CMD-SHELL", "bundle exec sidekiqmon processes | grep ${HOSTNAME}" ]
```

- Renamed directories used by app and sidekiq containers for gems cache to fix #339:

```diff
  dawarich_app:
    image: freikin/dawarich:latest
    container_name: dawarich_sidekiq
    volumes:
-     - gem_cache:/usr/local/bundle/gems
+     - gem_cache:/usr/local/bundle/gems_app

...

  dawarich_sidekiq:
    image: freikin/dawarich:latest
    container_name: dawarich_sidekiq
    volumes:
-     - gem_cache:/usr/local/bundle/gems
+     - gem_cache:/usr/local/bundle/gems_sidekiq
```

## [0.15.10] - 2024-10-25

### Fixed

- Data migration that prevented the application from starting.

## [0.15.9] - 2024-10-24

### Fixed

- Stats distance calculation now correctly calculates the daily distances.

### Changed

- Refactored the stats calculation process to make it more efficient.

## [0.15.8] - 2024-10-22

### Added

- User can now select between "Raw" and "Simplified" mode in the map controls. "Simplified" mode will show less points, improving the map performance. "Raw" mode will show all points.

## [0.15.7] - 2024-10-19

### Fixed

- A bug where "RuntimeError: failed to get urandom" was being raised upon importing attempt on Synology.

## [0.15.6] - 2024-10-19

### Fixed

- Import of Owntracks' .rec files now correctly imports points. Previously, the import was failing due to incorrect parsing of the file.

## [0.15.5] - 2024-10-16

### Fixed

- Fixed a bug where Google Takeout import was failing due to unsupported date format with milliseconds in the file.
- Fixed a bug that prevented using the Photon API host with http protocol. Now you can use both http and https protocols for the Photon API host. You now need to explicitly provide `PHOTON_API_USE_HTTPS` to be `true` or `false` depending on what protocol you want to use. [Example](https://github.com/Freika/dawarich/blob/master/docker-compose.yml#L116-L117) is in the `docker-compose.yml` file.

### Changed

- The Map page now by default uses timeframe based on last point tracked instead of the today's points. If there are no points, the map will use the today's timeframe.
- The map on the Map page can no longer be infinitely scrolled horizontally. #299

## [0.15.4] - 2024-10-15

### Changed

- Use static version of `geocoder` library that supports http and https for Photon API host. This is a temporary solution until the change is available in a stable release.

### Added

- Owntracks' .rec files now can be imported to Dawarich. The import process is the same as for other kinds of files, just select the .rec file and choose "owntracks" as a source.

### Removed

- Owntracks' .json files are no longer supported for import as Owntracks itself does not export to this format anymore.

## [0.15.3] - 2024-10-05

To expose the watcher functionality to the user, a new directory `/tmp/imports/watched/` was created. Add new volume to the `docker-compose.yml` file to expose this directory to the host machine.

```diff
  ...

  dawarich_app:
    image: freikin/dawarich:latest
    container_name: dawarich_app
    volumes:
      - gem_cache:/usr/local/bundle/gems
      - public:/var/app/public
+     - watched:/var/app/tmp/watched

  ...

  dawarich_sidekiq:
      image: freikin/dawarich:latest
      container_name: dawarich_sidekiq
      volumes:
        - gem_cache:/usr/local/bundle/gems
        - public:/var/app/public
+       - watched:/var/app/tmp/watched

    ...

volumes:
  db_data:
  gem_cache:
  shared_data:
  public:
+ watched:
```

### Changed

- Watcher now looks into `/tmp/imports/watched/USER@EMAIL.TLD` directory instead of `/tmp/imports/watched/` to allow using arbitrary file names for imports

## [0.15.1] - 2024-10-04

### Added

- `linux/arm/v7` is added to the list of supported architectures to support Raspberry Pi 4 and other ARMv7 devices

## [0.15.0] - 2024-10-03

The Watcher release

The /public/imporst/watched/ directory is watched by Dawarich. Any files you put in this directory will be imported into the database. The name of the file must start with an email of the user you want to import the file for. The email must be followed by an underscore symbol (_) and the name of the file.

For example, if you want to import a file for the user with the email address "email@dawarich.app", you would name the file "email@dawarich.app_2024-05-01_2024-05-31.gpx". The file will be imported into the database and the user will receive a notification in the app.

Both GeoJSON and GPX files are supported.


### Added

- You can now put your GPX and GeoJSON files to `tmp/imports/watched` directory and Dawarich will automatically import them. This is useful if you have a service that can put files to the directory automatically. The directory is being watched every 60 minutes for new files.

### Changed

- Monkey patch for Geocoder to support http along with https for Photon API host was removed becausee it was breaking the reverse geocoding process. Now you can use only https for the Photon API host. This might be changed in the future
- Disable retries for some background jobs

### Fixed

- Stats update is now being correctly triggered every 6 hours

## [0.14.7] - 2024-10-01

### Fixed

- Now you can use http protocol for the Photon API host if you don't have SSL certificate for it
- For stats, total distance per month might have been not equal to the sum of distances per day. Now it's fixed and values are equal
- Mobile view of the map looks better now


### Changed

- `GET /api/v1/points` can now accept optional `?order=asc` query parameter to return points in ascending order by timestamp. `?order=desc` is still available to return points in descending order by timestamp
- `GET /api/v1/points` now returns `id` attribute for each point

## [0.14.6] - 2024-29-30

### Fixed

- Points imported from Google Location History (mobile devise) now have correct timestamps

### Changed

- `GET /api/v1/points?slim=true` now returns `id` attribute for each point

## [0.14.5] - 2024-09-28

### Fixed

- GPX export now finishes correctly and does not throw an error in the end
- Deleting points from the Points page now preserves `start_at` and `end_at` values for the routes. #261
- Visits map now being rendered correctly in the Visits page. #262
- Fixed issue with timezones for negative UTC offsets. #194, #122
- Point page is no longer reloads losing provided timestamps when searching for points on Points page. #283

### Changed

- Map layers from Stadia were disabled for now due to necessary API key

## [0.14.4] - 2024-09-24

### Fixed

- GPX export now has time and elevation elements for each point

### Changed

- `GET /api/v1/points` will no longer return `raw_data` attribute for each point as it's a bit too much

### Added

- "Slim" version of `GET /api/v1/points`: pass optional param `?slim=true` to it and it will return only latitude, longitude and timestamp


## [0.14.3] - 2024-09-21

### Fixed

- Optimize order of the dockerfiles to leverage layer caching by @JoeyEamigh
- Add support for alternate postgres ports and db names in docker by @JoeyEamigh
- Creating exports directory if it doesn't exist by @tetebueno


## [0.14.1] - 2024-09-16

### Fixed

- Fixed a bug where the map was not loading due to invalid tile layer name


## [0.14.0] - 2024-09-15

### Added

- 17 new tile layers to choose from. Now you can select the tile layer that suits you the best. You can find the list of available tile layers in the map controls in the top right corner of the map under the layers icon.


## [0.13.7] - 2024-09-15

### Added

- `GET /api/v1/points` response now will include `X-Total-Pages` and `X-Current-Page` headers to make it easier to work with the endpoint
- The Pages point now shows total number of points found for provided date range

### Fixed

- Link to Visits page in notification informing about new visit suggestion


## [0.13.6] - 2024-09-13

### Fixed

- Flatten geodata retrieved from Immich before processing it to prevent errors


## [0.13.5] - 2024-09-08

### Added

- Links to view import points on the map and on the Points page on the Imports page.

### Fixed

- The Imports page now loading faster.

### Changed

- Default value for `RAILS_MAX_THREADS` was changed to 10.
- Visit suggestions background job was moved to its own low priority queue to prevent it from blocking other jobs.


## [0.13.4] - 2024-09-06

### Fixed

- Fixed a bug preventing the application from starting, when there is no users in the database but a data migration tries to update one.


## [0.13.3] - 2024-09-06

### Added

- Support for miles. To switch to miles, provide `DISTANCE_UNIT` environment variable with value `mi` in the `docker-compose.yml` file. Default value is `km`.

It's recommended to update your stats manually after changing the `DISTANCE_UNIT` environment variable. You can do this by clicking the "Update stats" button on the Stats page.

⚠️IMPORTANT⚠️: All settings are still should be provided in meters. All calculations though will be converted to feets and miles if `DISTANCE_UNIT` is set to `mi`.

```diff
  dawarich_app:
    image: freikin/dawarich:latest
    container_name: dawarich_app
    environment:
      APPLICATION_HOST: "localhost"
      APPLICATION_PROTOCOL: "http"
      APPLICATION_PORT: "3000"
      TIME_ZONE: "UTC"
+     DISTANCE_UNIT: "mi"
  dawarich_sidekiq:
    image: freikin/dawarich:latest
    container_name: dawarich_sidekiq
    environment:
      APPLICATION_HOST: "localhost"
      APPLICATION_PROTOCOL: "http"
      APPLICATION_PORT: "3000"
      TIME_ZONE: "UTC"
+     DISTANCE_UNIT: "mi"
```

### Changed

- Default time range on the map is now 1 day instead of 1 month. It will help you with performance issues if you have a lot of points in the database.


## [0.13.2] - 2024-09-06

### Fixed

- GeoJSON import now correctly imports files with FeatureCollection as a root object

### Changed

- The Points page now have number of points found for provided date range

## [0.13.1] - 2024-09-05

### Added

- `GET /api/v1/health` endpoint to check the health of the application with swagger docs

### Changed

- Ruby version updated to 3.3.4
- Visits suggestion process now will try to merge consecutive visits to the same place into one visit.


## [0.13.0] - 2024-09-03

The GPX and GeoJSON export release

⚠️ BREAKING CHANGES: ⚠️

Default exporting format is now GeoJSON instead of Owntracks-like JSON. This will allow you to use the exported data in other applications that support GeoJSON format. It's also important to highlight, that GeoJSON format does not describe a way to store any time-related data. Dawarich relies on the `timestamp` field in the GeoJSON format to determine the time of the point. The value of the `timestamp` field should be a Unix timestamp in seconds. If you import GeoJSON data that does not have a `timestamp` field, the point will not be imported.

Example of a valid point in GeoJSON format:

```json
{
  "type": "Feature",
  "geometry": {
    "type": "Point",
    "coordinates": [13.350110811262352, 52.51450815]
  },
  "properties": {
    "timestamp": 1725310036
  }
}
```

### Added

- GeoJSON format is now available for exporting data.
- GPX format is now available for exporting data.
- Importing GeoJSON is now available.

### Changed

- Default exporting format is now GeoJSON instead of Owntracks-like JSON. This will allow you to use the exported data in other applications that support GeoJSON format.

### Fixed

- Fixed a bug where the confirmation alert was shown more than once when deleting a point.


## [0.12.3] - 2024-09-02

### Added

- Resource limits to docke-compose.yml file to prevent server overload. Feel free to adjust the limits to your needs.

```yml
deploy:
  resources:
    limits:
      cpus: '0.50'    # Limit CPU usage to 50% of one core
      memory: '2G'    # Limit memory usage to 2GB
```

### Fixed

- Importing geodata from Immich will now not throw an error in the end of the process

### Changed

- A notification about an existing import with the same name will now show the import name
- Export file now also will contain `raw_dat` field for each point. This field contains the original data that was imported to the application.


## [0.12.2] - 2024-08-28

### Added

- `PATCH /api/v1/settings` endpoint to update user settings with swagger docs
- `GET /api/v1/settings` endpoint to get user settings with swagger docs
- Missing `page` and `per_page` query parameters to the `GET /api/v1/points` endpoint swagger docs

### Changed

- Map settings moved to the map itself and are available in the top right corner of the map under the gear icon.


## [0.12.1] - 2024-08-25

### Fixed

- Fixed a bug that prevented data migration from working correctly

## [0.12.0] - 2024-08-25

### The visit suggestion release

1. With this release deployment, data migration will work, starting visits suggestion process for all users.
2. After initial visit suggestion process, new suggestions will be calculated every 24 hours, based on points for last 24 hours.
3. If you have enabled reverse geocoding and (optionally) provided Photon Api Host, Dawarich will try to reverse geocode your visit and suggest specific places you might have visited, such as cafes, restaurants, parks, etc. If reverse geocoding is not enabled, or Photon Api Host is not provided, Dawarich will not try to suggest places but you'll be able to rename the visit yourself.
4. You can confirm or decline the visit suggestion. If you confirm the visit, it will be added to your timeline. If you decline the visit, it will be removed from your timeline. You'll be able to see all your confirmed, declined and suggested visits on the Visits page.


### Added

- A "Map" button to each visit on the Visits page to allow user to see the visit on the map
- Visits suggestion functionality. Read more on that in the release description
- Click on the visit name allows user to rename the visit
- Tabs to the Visits page to allow user to switch between confirmed, declined and suggested visits
- Places page to see and delete places suggested by Dawarich's visit suggestion process
- Importing a file will now trigger the visit suggestion process for the user

## [0.11.2] - 2024-08-22

### Changed

### Fixed

- Dawarich export was failing when attempted to be imported back to Dawarich.
- Imports page with a lot of imports should now load faster.


## [0.11.1] - 2024-08-21

### Changed

- `/api/v1/points` endpoint now returns 100 points by default. You can specify the number of points to return by passing the `per_page` query parameter. Example: `/api/v1/points?per_page=50` will return 50 points. Also, `page` query parameter is now available to paginate the results. Example: `/api/v1/points?per_page=50&page=2` will return the second page of 50 points.

## [0.11.0] - 2024-08-21

### Added

- A user can now trigger the import of their geodata from Immich to Dawarich by clicking the "Import Immich data" button in the Imports page.
- A user can now provide a url and an api key for their Immich instance and then trigger the import of their geodata from Immich to Dawarich. This can be done in the Settings page.

### Changed

- Table columns on the Exports page were reordered to make it more user-friendly.
- Exports are now being named with this pattern: "export_from_dd.mm.yyyy_to_dd.mm.yyyy.json" where "dd.mm.yyyy" is the date range of the export.
- Notification about any error now will include the stacktrace.

## [0.10.0] - 2024-08-20

### Added

- The `api/v1/stats` endpoint to get stats for the user with swagger docs

### Fixed

- Redis and DB containers are now being automatically restarted if they fail. Update your `docker-compose.yml` if necessary

```diff
  services:
  dawarich_redis:
    image: redis:7.0-alpine
    command: redis-server
    networks:
      - dawarich
    volumes:
      - shared_data:/var/shared/redis
+   restart: always
  dawarich_db:
    image: postgis/postgis:14-3.5-alpine
    container_name: dawarich_db
    volumes:
      - db_data:/var/lib/postgresql/data
      - shared_data:/var/shared
    networks:
      - dawarich
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
+   restart: always
```


See the [PR](https://github.com/Freika/dawarich/pull/185) or Swagger docs (`/api-docs`) for more information.

## [0.9.12] - 2024-08-15

### Fixed

- Owntracks points are now being saved to the database with the full attributes
- Existing owntracks points also filled with missing data
- Definition of "reverse geocoded points" is now correctly based on the number of points that have full reverse geocoding data instead of the number of points that have only country and city
- Fixed a bug in gpx importing scipt ([thanks, bluemax!](https://github.com/Freika/dawarich/pull/126))

## [0.9.11] - 2024-08-14

### Fixed

- A bug where an attempt to import a Google's Records.json file was failing due to wrong object being passed to a background worker

## [0.9.10] - 2024-08-14

### Added

- PHOTON_API_HOST env variable to set the host of the Photon API. It will allow you to use your own Photon API instance instead of the default one.

## [0.9.9] - 2024-07-30

### Added

- Pagination to exports page
- Pagination to imports page
- GET `/api/v1/points` endpoint to get all points for the user with swagger docs
- DELETE `/api/v1/points/:id` endpoint to delete a single point for the user with swagger docs
- DELETE `/api/v1/areas/:id` swagger docs
- User can now change route opacity in settings
- Points on the Points page can now be ordered by oldest or newest points
- Visits on the Visits page can now be ordered by oldest or newest visits

### Changed

- Point deletion is now being done using an api key instead of CSRF token

### Fixed

- OpenStreetMap layer is now being selected by default in map controls

---

## [0.9.8] - 2024-07-27

### Fixed

- Call to the background job to calculate visits

---

## [0.9.7] - 2024-07-27

### Fixed

- Name of background job to calculate visits

---

## [0.9.6] - 2024-07-27

### Fixed

- Map areas functionality

---

## [0.9.5] - 2024-07-27

### Added

- A possibility to create areas. To create an area, click on the Areas checkbox in map controls (top right corner of the map), then in the top left corner of the map, click on a small circle icon. This will enable draw tool, allowing you to draw an area. When you finish drawing, release the mouse button, and the area will be created. Click on the area, set the name and click "Save" to save the area. You can also delete the area by clicking on the trash icon in the area popup.
- A background job to calculate your visits. This job will calculate your visits based on the areas you've created.
- Visits page. This page will show you all your visits, calculated based on the areas you've created. You can see the date and time of the visit, the area you've visited, and the duration of the visit.
- A possibility to confirm or decline a visit. When you create an area, the visit is not calculated immediately. You need to confirm or decline the visit. You can do this on the Visits page. Click on the visit, then click on the "Confirm" or "Decline" button. If you confirm the visit, it will be added to your timeline. If you decline the visit, it will be removed from your timeline.
- Settings for visit calculation. You can set the minimum time spent in the area to consider it as a visit. This setting can be found in the Settings page.
- POST `/api/v1/areas` and GET `/api/v1/areas` endpoints. You can now create and list your areas via the API.

⚠️ Visits functionality is still in beta. If you find any issues, please let me know. ⚠️

### Fixed

- A route popup now correctly shows distance made in the route, not the distance between first and last points in the route.

---

## [0.9.4] - 2024-07-21

### Added

- A popup being shown when user clicks on a point now contains a link to delete the point. This is useful if you want to delete a point that was imported by mistake or you just want to clean up your data.

### Fixed

- Added `public/imports` and `public/exports` folders to git to prevent errors when exporting data

### Changed

- Some code from `maps_controller.js` was extracted into separate files

---


## [0.9.3] - 2024-07-19

### Added

- Admin flag to the database. Now not only the first user in the system can create new users, but also users with the admin flag set to true. This will make easier introduction of more admin functions in the future.

### Fixed

- Route hover distance is now being rendered in kilometers, not in meters, if route distance is more than 1 km.

---

## [0.9.2] - 2024-07-19

### Fixed

- Hover over a route does not move map anymore and shows the route tooltip where user hovers over the route, not at the end of the route. Click on route now will move the map to include the whole route.

---

## [0.9.1] - 2024-07-12

### Fixed

- Fixed a bug where total reverse geocoded points were calculated based on number of *imported* points that are reverse geocoded, not on the number of *total* reverse geocoded points.

---

## [0.9.0] - 2024-07-12

### Added

- Background jobs page. You can find it in Settings -> Background Jobs.
- Queue clearing buttons. You can clear all jobs in the queue.
- Reverse geocoding restart button. You can restart the reverse geocoding process for all of your points.
- Reverse geocoding continue button. Click on this button will start reverse geocoding process only for points that were not processed yet.
- A lot more data is now being saved in terms of reverse geocoding process. It will be used in the future to create more insights about your data.

### Changed

- Point reference to a user is no longer optional. It should not cause any problems, but if you see any issues, please let me know.
- ⚠️ Calculation of total reverse geocoded points was changed. ⚠️ Previously, the reverse geocoding process was recording only country and city for each point. Now, it records all the data that was received from the reverse geocoding service. This means that the total number of reverse geocoded points will be different from the previous one. It is recommended to restart the reverse geocoding process to get this data for all your existing points. Below you can find an example of what kind of data is being saved to your Dawarich database:

```json
{
  "place_id": 127850637,
  "licence": "Data © OpenStreetMap contributors, ODbL 1.0. http://osm.org/copyright",
  "osm_type": "way",
  "osm_id": 718035022,
  "lat": "52.51450815",
  "lon": "13.350110811262352",
  "class": "historic",
  "type": "monument",
  "place_rank": 30,
  "importance": 0.4155071896625501,
  "addresstype": "historic",
  "name": "Victory Column",
  "display_name": "Victory Column, Großer Stern, Botschaftsviertel, Tiergarten, Mitte, Berlin, 10785, Germany",
  "address": {
    "historic": "Victory Column",
    "road": "Großer Stern",
    "neighbourhood": "Botschaftsviertel",
    "suburb": "Tiergarten",
    "borough": "Mitte",
    "city": "Berlin",
    "ISO3166-2-lvl4": "DE-BE",
    "postcode": "10785",
    "country": "Germany",
    "country_code": "de"
  },
  "boundingbox": [
    "52.5142449",
    "52.5147775",
    "13.3496725",
    "13.3505485"
  ]
}
```

---

## [0.8.7] - 2024-07-09

### Changed

- Added a logging config to the `docker-compose.yml` file to prevent logs from overflowing the disk. Now logs are being rotated and stored in the `log` folder in the root of the application. You can find usage example in the the repository's `docker-compose.yml` [file](https://github.com/Freika/dawarich/blob/master/docker-compose.yml#L50). Make sure to add this config to both `dawarich_app` and `dawarich_sidekiq` services.

```yaml
  logging:
      driver: "json-file"
      options:
        max-size: "100m"
        max-file: "5"
```

### Fixed

- Visiting notifications page now marks this notifications as read

---

## [0.8.6] - 2024-07-08

### Added

- Guide on how to setup a reverse proxy for Dawarich in the `docs/how_to_setup_reverse_proxy.md` file. This guide explains how to set up a reverse proxy for Dawarich using Nginx and Apache2.

### Removed

- `MAP_CENTER` env var from the `docker-compose.yml` file. This variable was used to set the default center of the map, but it is not needed anymore, as the map center is now hardcoded in the application. ⚠️ Feel free to remove this variable from your `docker-compose.yml` file. ⚠️

### Fixed

- Fixed a bug where Overland batch payload was not being processed due to missing coordinates in the payload. Now, if the coordinates are missing, the single point is skipped and the rest are being processed.

---

## [0.8.5] - 2024-07-08

### Fixed

- Set `'localhost'` string as a default value for `APPLICATION_HOSTS` environment variable in the `docker-compose.yml` file instead of an array. This is necessary to prevent errors when starting the application.

---

## [0.8.4] - 2024-07-08

### Added

- Support for multiple hosts. Now you can specify the host of the application by setting the `APPLICATION_HOSTS` (note plural form) environment variable in the `docker-compose.yml` file. Example:

```yaml
  dawarich_app:
    image: freikin/dawarich:latest
    container_name: dawarich_app
    environment:
      APPLICATION_HOSTS: "yourhost.com,www.yourhost.com,127.0.0.1"
```

Note, there should be no protocol prefixes in the `APPLICATION_HOSTS` variable, only the hostnames.

⚠️ It would also be better to migrate your current `APPLICATION_HOST` to `APPLICATION_HOSTS` to avoid any issues in the future, as `APPLICATION_HOST` will be deprecated in the nearest future. ⚠️

- Support for HTTPS. Now you can specify the protocol of the application by setting the `APPLICATION_PROTOCOL` environment variable in the `docker-compose.yml` file. Default value is `http` Example:

```yaml
  dawarich_app:
    image: freikin/dawarich:latest
    container_name: dawarich_app
    environment:
      APPLICATION_PROTOCOL: "https"
```

### Fixed

- Support for a `location-history.json` file from Google Takeout. It turned out, this file could contain not only an object with location data history, but also an array of objects with location data history. Now Dawarich can handle both cases and import the data correctly.


---

## [0.8.3] - 2024-07-03

### Added

- Notifications system. Now you will receive a notification when an import or export is finished, when stats update is completed and if any error occurs during any of these processes. Notifications are displayed in the top right corner of the screen and are stored in the database. You can see all your notifications on the Notifications page.
- Swagger API docs for `/api/v1/owntracks/points`. You can find the API docs at `/api-docs`.

---

## [0.8.2] - 2024-06-30

### Added

- Google Takeout geodata, taken from a [mobile devise](https://support.google.com/maps/thread/264641290/export-full-location-timeline-data-in-json-or-similar-format-in-the-new-version-of-timeline?hl=en), is now fully supported and can be imported to the Dawarich. The import process is the same as for other kinds of files, just select the JSON file and choose "Google Phone Takeout" as a source.

### Fixed

- Fixed a bug where an imported point was not being saved to the database if a point with the same timestamp and already existed in the database even if it was other user's point.

---

## [0.8.1] - 2024-06-30

### Added

- First user in the system can now create new users from the Settings page. This is useful for creating new users without the need to enable registrations. Default password for new users is `password`.

### Changed

- Registrations are now disabled by default. On the initial setup, a default user with email `user@domain.com` and password `password` is created. You can change the password in the Settings page.
- On the Imports page, now you can see the real number of points imported. Previously, this number might have not reflect the real number of points imported.

---

## [0.8.0] - 2024-06-25

### Added

- New Settings page to change Dawarich settings.
- New "Fog of War" toggle on the map controls.
- New "Fog of War meters" field in Settings. This field allows you to set the radius in meters around the point to be shown on the map. The map outside of this radius will be covered with a fog of war.

### Changed

- Order of points on Points page is now descending by timestamp instead of ascending.

---

## [0.7.1] - 2024-06-20

In new Settings page you can now change the following settings:

- Maximum distance between two points to consider them as one route
- Maximum time between two points to consider them as one route

### Added

- New Settings page to change Dawarich settings.

### Changed

- Settings link in user menu now redirects to the new Settings page.
- Old settings page is now available undeer Account link in user menu.

---

## [0.7.0] - 2024-06-19

The GPX MVP Release

This release introduces support for GPX files to be imported. Now you can import GPX files from your devices to Dawarich. The import process is the same as for other kinds of files, just select the GPX file instead and choose "gpx" as a source. Both single-segmented and multi-segmented GPX files are supported.

⚠️ BREAKING CHANGES: ⚠️

- `/api/v1/points` endpoint is removed. Please use `/api/v1/owntracks/points` endpoint to upload your points from OwnTracks mobile app instead.

### Added

- Support for GPX files to be imported.

### Changed

- Couple of unnecessary params were hidden from route popup and now can be shown using `?debug=true` query parameter. This is useful for debugging purposes.

### Removed

- `/exports/download` endpoint is removed. Now you can download your exports directly from the Exports page.
- `/api/v1/points` endpoint is removed.

---

## [0.6.4] - 2024-06-18

### Added

- A link to Dawarich's website in the footer. It ain't much, but it's honest work.

### Fixed

- Fixed version badge in the navbar. Now it will show the correct version of the application.

### Changed

- Default map center location was changed.

---

## [0.6.3] - 2024-06-14

⚠️ IMPORTANT: ⚠️

Please update your `docker-compose.yml` file to include the following changes:

```diff
  dawarich_sidekiq:
    image: freikin/dawarich:latest
    container_name: dawarich_sidekiq
    volumes:
      - gem_cache:/usr/local/bundle/gems
+     - public:/var/app/public
```

### Added

- Added a line with public volume to sidekiq's docker-compose service to allow sidekiq process to write to the public folder

### Fixed

- Fixed a bug where the export file was not being created in the public folder

---

## [0.6.2] - 2024-06-14

This is a debugging release. No changes were made to the application.

---

## [0.6.0] - 2024-06-12

### Added

- Exports page to list existing exports download them or delete them

### Changed

- Exporting process now is done in the background, so user can close the browser tab and come back later to download the file. The status of the export can be checked on the Exports page.

ℹ️ Deleting Export file will only delete the file, not the points in the database. ℹ️

⚠️ BREAKING CHANGES: ⚠️

Volume, exposed to the host machine for placing files to import was changed. See the changes below.

Path for placing files to import was changed from `tmp/imports` to `public/imports`.

```diff
  ...

  dawarich_app:
    image: freikin/dawarich:latest
    container_name: dawarich_app
    volumes:
      - gem_cache:/usr/local/bundle/gems
-     - tmp:/var/app/tmp
+     - public:/var/app/public/imports

  ...
```

```diff
  ...

volumes:
  db_data:
  gem_cache:
  shared_data:
- tmp:
+ public:
```

---

## [0.5.3] - 2024-06-10

### Added

- A data migration to remove points with 0.0, 0.0 coordinates. This is necessary to prevent errors when calculating distance in Stats page.

### Fixed

- Reworked code responsible for importing "Records.json" file from Google Takeout. Now it is more reliable and faster, and should not throw as many errors as before.

---

## [0.5.2] - 2024-06-08

### Added

- Test version of google takeout importing service for exports from users' phones

---

## [0.5.1] - 2024-06-07

### Added

- Background jobs concurrency now can be set with `BACKGROUND_PROCESSING_CONCURRENCY` env variable in `docker-compose.yml` file. Default value is 10.
- Hand-made favicon

### Changed

- Change minutes to days and hours on route popup

### Fixed

- Improved speed of "Stats" page loading by removing unnecessary queries

---

## [0.5.0] - 2024-05-31

### Added

- New buttons to quickly move to today's, yesterday's and 7 days data on the map
- "Download JSON" button to points page
- For debugging purposes, now user can use `?meters_between_routes=500` and `?minutes_between_routes=60` query parameters to set the distance and time between routes to split them on the map. This is useful to understand why routes might not be connected on the map.
- Added scale indicator to the map

### Changed

- Removed "Your data" page as its function was replaced by "Download JSON" button on the points page
- Hovering over a route now also shows time and distance to next route as well as time and distance to previous route. This allows user to understand why routes might not be connected on the map.

---

## [0.4.3] - 2024-05-30

### Added

- Now user can hover on a route and see when it started, when it ended and how much time it took to travel

### Fixed

- Timestamps in export form are now correctly assigned from the first and last points tracked by the user
- Routes are now being split based both on distance and time. If the time between two consecutive points is more than 60 minutes, the route is split into two separate routes. This improves visibility of the routes on the map.

---

## [0.4.2] - 2024-05-29

### Changed

- Routes are now being split into separate one. If distance between two consecutive points is more than 500 meters, the route is split into two separate routes. This improves visibility of the routes on the map.
- Background jobs concurrency is increased from 5 to 10 to speed up the processing of the points.

### Fixed

- Point data, accepted from OwnTracks and Overland, is now being checked for duplicates. If a point with the same timestamp and coordinates already exists in the database, it will not be saved.

---
## [0.4.1] - 2024-05-25

### Added

- Heatmap layer on the map to show the density of points

---

## [0.4.0] - 2024-05-25

**BREAKING CHANGES**:

- `/api/v1/points` is still working, but will be **deprecated** in nearest future. Please use `/api/v1/owntracks/points` instead.
- All existing points recorded directly to the database via Owntracks or Overland will be attached to the user with id 1.

### Added

- Each user now have an api key, which is required to make requests to the API. You can find your api key in your profile settings.
- You can re-generate your api key in your profile settings.
- In your user profile settings you can now see the instructions on how to use the API with your api key for both OwnTracks and Overland.
- Added docs on how to use the API with your api key. Refer to `/api-docs` for more information.
- `POST /api/v1/owntracks/points` endpoint.
- Points are now being attached to a user directly, so you can only see your own points and no other users of your applications can see your points.

### Changed

- `/api/v1/overland/batches` endpoint now requires an api key to be passed in the url. You can find your api key in your profile settings.
- All existing points recorded directly to the database will be attached to the user with id 1.
- All stats and maps are now being calculated and rendered based on the user's points only.
- Default `TIME_ZONE` environment variable is now set to 'UTC' in the `docker-compose.yml` file.

### Fixed

- Fixed a bug where marker on the map was rendering timestamp without considering the timezone.

---

## [0.3.2] - 2024-05-23

### Added

- Docker volume for importing Google Takeout data to the application

### Changed

- Instruction on how to import Google Takeout data to the application

---

## [0.3.1] - 2024-05-23

### Added

- Instruction on how to import Google Takeout data to the application

---

## [0.3.0] - 2024-05-23

### Added

- Add Points page to display all the points as a table with pagination to allow users to delete points
- Sidekiq web interface to monitor background jobs is now available at `/sidekiq`
- Now you can choose a date range of points to be exported

---

## [0.2.6] - 2024-05-23

### Fixed

- Stop selecting `raw_data` column during requests to `imports` and `points` tables to improve performance.

### Changed

- Rename PointsController to MapController along with all the views and routes

### Added

- Add Points page to display all the points as a table with pagination to allow users to delete points

---

## [0.2.5] - 2024-05-21

### Fixed

- Stop ignoring `raw_data` column during requests to `imports` and `points` tables. This was preventing points from being created.

---

## [0.2.4] - 2024-05-19

### Added

- In right sidebar you can now see the total amount of geopoints aside of kilometers traveled

### Fixed

- Improved overall performance if the application by ignoring `raw_data` column during requests to `imports` and `points` tables.

---


## [0.2.3] - 2024-05-18

### Added

- Now you can import `records.json` file from your Google Takeout archive, not just Semantic History Location JSON files. The import process is the same as for Semantic History Location JSON files, just select the `records.json` file instead and choose "google_records" as a source.

---


## [0.2.2] - 2024-05-18

### Added

- Swagger docs, can be found at `https:<your-host>/api-docs`

---

## [0.2.1] - 2024-05-18

### Added

- Cities, visited by user and listed in right sidebar now also have an active link to a date they were visited

### Fixed

- Dark/light theme switcher in navbar is now being saved in user settings, so it persists between sessions

---

## [0.2.0] - 2024-05-05

*Breaking changes:*

This release changes how Dawarich handles a city visit threshold. Previously, the `MINIMUM_POINTS_IN_CITY` environment variable was used to determine the minimum *number of points* in a city to consider it as visited. Now, the `MIN_MINUTES_SPENT_IN_CITY` environment variable is used to determine the minimum *minutes* between two points to consider them as visited the same city.

The logic behind this is the following: if you have a lot of points in a city, it doesn't mean you've spent a lot of time there, especially if your OwnTracks app was in "Move" mode. So, it's better to consider the time spent in a city rather than the number of points.

In your docker-compose.yml file, you need to replace the `MINIMUM_POINTS_IN_CITY` environment variable with `MIN_MINUTES_SPENT_IN_CITY`. The default value is `60`, in minutes.

---

## [0.1.9] - 2024-04-25

### Added

- A test for CheckAppVersion service class

### Changed

- Replaced ActiveStorage with Shrine for file uploads

### Fixed

- `ActiveStorage::FileNotFoundError` error when uploading export files

---

## [0.1.8.1] - 2024-04-21

### Changed

- Set Redis as default cache store

### Fixed

- Consider timezone when parsing datetime params in points controller
- Add rescue for check version service class

---

## [0.1.8] - 2024-04-21

### Added

- Application version badge to the navbar with check for updates button
- Npm dependencies install to Github build workflow
- Footer

### Changed

- Disabled map points rendering by default to improve performance on big datasets

---

## [0.1.7] - 2024-04-17

### Added

- Map controls to toggle polylines and points visibility

### Changed

- Added content padding for mobile view
- Fixed stat card layout for mobile view

---

## [0.1.6.3] - 2024-04-07

### Changed

- Removed strong_params from POST /api/v1/points

---

## [0.1.6.1] - 2024-04-06

### Fixed

- `ActiveStorage::FileNotFoundError: ActiveStorage::FileNotFoundError` error when uploading export files

---

## [0.1.6] - 2024-04-06

You can now use [Overland](https://overland.p3k.app/) mobile app to track your location.

### Added

- Overland API endpoint (POST /api/v1/overland/batches)

### Changed

### Fixed

---

## [0.1.5] - 2024-04-05

You can now specify the host of the application by setting the `APPLICATION_HOST` environment variable in the `docker-compose.yml` file.

### Added

- Added version badge to navbar
- Added APPLICATION_HOST environment variable to docker-compose.yml to allow user to specify the host of the application
- Added CHANGELOG.md to keep track of changes

### Changed

- Specified gem version in Docker entrypoint

### Fixed


================================================
FILE: CLAUDE.md
================================================
# CLAUDE.md - Dawarich Development Guide

This file contains essential information for Claude to work effectively with the Dawarich codebase.

## Project Overview

**Dawarich** is a self-hostable web application built with Ruby on Rails 8.0 that serves as a replacement for Google Timeline (Google Location History). It allows users to track, visualize, and analyze their location data through an interactive web interface.

### Key Features
- Location history tracking and visualization
- Interactive maps with multiple layers (heatmap, points, lines, fog of war)
- Import from various sources (Google Maps Timeline, OwnTracks, Strava, GPX, GeoJSON, photos)
- Export to GeoJSON and GPX formats
- Statistics and analytics (countries visited, distance traveled, etc.)
- Public sharing of monthly statistics with time-based expiration
- Trips management with photo integration
- Areas and visits tracking
- Integration with photo management systems (Immich, Photoprism)

## Technology Stack

### Backend
- **Framework**: Ruby on Rails 8.0
- **Database**: PostgreSQL with PostGIS extension
- **Background Jobs**: Sidekiq with Redis
- **Authentication**: Devise
- **Authorization**: Pundit
- **API Documentation**: rSwag (Swagger)
- **Monitoring**: Prometheus, Sentry
- **File Processing**: AWS S3 integration

### Frontend
- **CSS Framework**: Tailwind CSS with DaisyUI components
- **JavaScript**: Stimulus, Turbo Rails, Hotwired
- **Maps**: Leaflet.js
- **Charts**: Chartkick

## Conventions
- **Enums over strings:** Prefer Rails enums (integer columns) over string columns for status/type fields. Use `enum :field_name, { ... }, prefix: :field_name` to get scoped predicate methods and avoid name collisions.
- **Turbo first:** Follow Rails 8 conventions — use Turbo Frames and Turbo Streams/broadcasts wherever appropriate to avoid full page reloads and provide smooth, in-place UI updates.
- **SVGs as files:** Never inline SVG markup in views. Instead, save SVGs to `app/assets/svg/icons` and use `inline_svg_tag "name.svg"` to render them. This keeps views clean and SVGs reusable. Use `rails_icons` to manage SVG assets and ensure consistent styling.

## Code Style

- Follow rubocop conventions (see `.rubocop.yml`)
- Rails defaults: convention over configuration
- Prefer Hotwire (Turbo Frames/Streams + Stimulus) over custom JS
- Use importmap for JS dependencies — no npm/yarn

### Key Gems
- `activerecord-postgis-adapter` - PostgreSQL PostGIS support
- `geocoder` - Geocoding services
- `rgeo` - Ruby Geometric Library
- `gpx` - GPX file processing
- `parallel` - Parallel processing
- `sidekiq` - Background job processing
- `chartkick` - Chart generation

## Project Structure

```
├── app/
│   ├── controllers/     # Rails controllers
│   ├── models/         # ActiveRecord models with PostGIS support
│   ├── views/          # ERB templates
│   ├── services/       # Business logic services
│   ├── jobs/           # Sidekiq background jobs
│   ├── queries/        # Database query objects
│   ├── policies/       # Pundit authorization policies
│   ├── serializers/    # API response serializers
│   ├── javascript/     # Stimulus controllers and JS
│   └── assets/         # CSS and static assets
├── config/             # Rails configuration
├── db/                 # Database migrations and seeds
├── docker/             # Docker configuration
├── spec/               # RSpec test suite
└── swagger/            # API documentation
```

## Core Models

### Primary Models
- **User**: Authentication and user management
- **Point**: Individual location points with coordinates and timestamps
- **Track**: Collections of related points forming routes
- **Area**: Geographic areas drawn by users
- **Visit**: Detected visits to areas
- **Trip**: User-defined travel periods with analytics
- **Import**: Data import operations
- **Export**: Data export operations
- **Stat**: Calculated statistics and metrics with public sharing capabilities

### Geographic Features
- Uses PostGIS for advanced geographic queries
- Implements distance calculations and spatial relationships
- Supports various coordinate systems and projections

## Development Environment

### Setup
1. **Docker Development**: Use `docker-compose -f docker/docker-compose.yml up`
2. **DevContainer**: VS Code devcontainer support available
3. **Local Development**:
   - `bundle exec rails db:prepare`
   - `bundle exec sidekiq` (background jobs)
   - `bundle exec bin/dev` (main application)

### Default Credentials
- Username: `demo@dawarich.app`
- Password: `password`

## Testing

### Test Suite
- **Framework**: RSpec
- **System Tests**: Capybara + Selenium WebDriver
- **E2E Tests**: Playwright
- **Coverage**: SimpleCov
- **Factories**: FactoryBot
- **Mocking**: WebMock

### Test Commands
```bash
bundle exec rspec                    # Run all specs
bundle exec rspec spec/models/       # Model specs only
npx playwright test                  # E2E tests
```

### Testing Best Practices — Test Behavior, Not Implementation

When writing or modifying tests, always test **observable behavior** (return values, state changes, side effects) rather than **implementation details** (which internal methods are called, in what order, with what exact arguments).

**Anti-patterns to AVOID:**

1. **Never mock the object under test** — `allow(subject).to receive(:internal_method)` makes the test a tautology
2. **Never test private methods via `send()`** — test through the public interface instead; if creating a user triggers a trial, test by creating the user and checking `user.trial?`, not by calling `user.send(:start_trial)`
3. **Never use `receive_message_chain`** — `allow(x).to receive_message_chain(:a, :b, :c)` breaks on any scope reorder; use real data instead
4. **Avoid over-stubbing** — if every collaborator is mocked, the test proves nothing; mock only at external boundaries (HTTP, geocoder, external APIs)
5. **Don't test wiring without outcomes** — `expect(Service).to receive(:new).with(args)` only proves a method was called, not that it works; verify the returned data or state change instead
6. **Prefer `have_enqueued_job` over `expect(Job).to receive(:perform_later)`** — the former tests real ActiveJob integration; the latter just tests a mock
7. **Don't assert on cache key formats or internal metric JSON shapes** — test that caching works (2nd call doesn't requery) or that metrics fire, not exact internal formats
8. **Use real factory data over `allow(user).to receive(:active?).and_return(true)`** — set the actual user state: `create(:user, status: :active)`

**Good test pattern:**
```ruby
# Test behavior: creating an export enqueues processing
it 'enqueues processing job' do
  expect { create(:export, file_type: :points) }.to have_enqueued_job(ExportJob)
end
```

**Bad test pattern:**
```ruby
# Tests implementation: mocks the callback interaction
it 'enqueues processing job' do
  expect(ExportJob).to receive(:perform_later)  # mock, not real
  build(:export).save!
end
```

## Background Jobs

### Sidekiq Jobs
- **Import Jobs**: Process uploaded location data files
- **Calculation Jobs**: Generate statistics and analytics
- **Notification Jobs**: Send user notifications
- **Photo Processing**: Extract EXIF data from photos

### Key Job Classes
- `Tracks::ParallelGeneratorJob` - Generate track data in parallel
- Various import jobs for different data sources
- Statistical calculation jobs

## Public Sharing System

### Overview
Dawarich includes a comprehensive public sharing system that allows users to share their monthly statistics with others without requiring authentication. This feature enables users to showcase their location data while maintaining privacy control through configurable expiration settings.

### Key Features
- **Time-based expiration**: Share links can expire after 1 hour, 12 hours, 24 hours, or be permanent
- **UUID-based access**: Each shared stat has a unique, unguessable UUID for security
- **Public API endpoints**: Hexagon map data can be accessed via API without authentication when sharing is enabled
- **Automatic cleanup**: Expired shares are automatically inaccessible
- **Privacy controls**: Users can enable/disable sharing and regenerate sharing URLs at any time

### Technical Implementation
- **Database**: `sharing_settings` (JSONB) and `sharing_uuid` (UUID) columns on `stats` table
- **Routes**: `/shared/month/:uuid` for public viewing, `/stats/:year/:month/sharing` for management
- **API**: `/api/v1/maps/hexagons` supports public access via `uuid` parameter
- **Controllers**: `Shared::StatsController` handles public views, sharing management integrated into existing stats flow

### Security Features
- **No authentication bypass**: Public sharing only exposes specifically designed endpoints
- **UUID-based access**: Sharing URLs use unguessable UUIDs rather than sequential IDs
- **Expiration enforcement**: Automatic expiration checking prevents access to expired shares
- **Limited data exposure**: Only monthly statistics and hexagon data are publicly accessible

### Usage Patterns
- **Social sharing**: Users can share interesting travel months with friends and family
- **Portfolio/showcase**: Travel bloggers and photographers can showcase location statistics
- **Data collaboration**: Researchers can share aggregated location data for analysis
- **Public demonstrations**: Demo instances can provide public examples without compromising user data

## API Documentation

- **Framework**: rSwag (Swagger/OpenAPI)
- **Location**: `/api-docs` endpoint
- **Authentication**: API key (Bearer) for API access, UUID-based access for public shares

## Database Schema

### Key Tables
- `users` - User accounts and settings
- `points` - Location points with PostGIS geometry
- `tracks` - Route collections
- `areas` - User-defined geographic areas
- `visits` - Detected area visits
- `trips` - Travel periods
- `imports`/`exports` - Data transfer operations
- `stats` - Calculated metrics with sharing capabilities (`sharing_settings`, `sharing_uuid`)

### PostGIS Integration
- Extensive use of PostGIS geometry types
- Spatial indexes for performance
- Geographic calculations and queries

## Configuration

### Environment Variables
See `.env.template` for available configuration options including:
- Database configuration
- Redis settings
- AWS S3 credentials
- External service integrations
- Feature flags

### Key Config Files
- `config/database.yml` - Database configuration
- `config/sidekiq.yml` - Background job settings
- `config/schedule.yml` - Cron job schedules
- `docker/docker-compose.yml` - Development environment

## Deployment

### Docker
- Production: `docker/docker-compose.production.yml`
- Development: `docker/docker-compose.yml`
- Multi-stage Docker builds supported

### Procfiles
- `Procfile` - Production Heroku deployment
- `Procfile.dev` - Development with Foreman
- `Procfile.production` - Production processes

## Code Quality

### Tools
- **Ruby Linting**: RuboCop with Rails extensions
- **JS/CSS Linting**: Biome (formatting, lint, import sorting)
- **Security**: Brakeman, bundler-audit
- **Dependencies**: Strong Migrations for safe database changes
- **Performance**: Stackprof for profiling

### Commands
```bash
bundle exec rubocop                  # Ruby linting
npx @biomejs/biome check --write .   # JS/CSS auto-fix (safe fixes)
npx @biomejs/biome check --write --unsafe .  # JS/CSS auto-fix (all fixes)
npx @biomejs/biome ci .              # JS/CSS CI check (read-only)
bundle exec brakeman                 # Security scan
bundle exec bundle-audit             # Dependency security
```

### Lint Rules
- **Always run RuboCop** on modified Ruby files before committing: `bundle exec rubocop <files>`
- **Always run Biome** on modified JS/CSS files before committing: `npx @biomejs/biome check --write <files>`
- If Biome `--write` leaves remaining errors, use `--write --unsafe` to apply fixes like `parseInt` radix and `Number.isNaN`
- CI runs `biome ci --changed --since=dev` — it compares against the `dev` branch, not `master`
- The `noStaticOnlyClass` warning is acceptable and does not fail CI
- Tailwind CSS files (`*.tailwind.css`) have `@import` position rules disabled in `biome.json` because `@tailwind` directives must come first

## Frontend: Hotwire-First Approach

**Always prefer Turbo + Stimulus over custom JavaScript.** This project uses the Hotwire stack (Turbo Drive, Turbo Frames, Turbo Streams, Stimulus) as its primary frontend architecture. Direct `fetch()` calls, manual DOM manipulation, and standalone JS modules should only be used when Hotwire cannot handle the use case (e.g., map rendering with Leaflet/MapLibre).

### Decision Hierarchy

When adding frontend behavior, follow this order of preference:

1. **Turbo Drive** — Default. Links and forms work as SPAs with zero JS.
2. **Turbo Frames** — Partial page updates. Wrap a section in `<turbo-frame>` and target it from links/forms.
3. **Turbo Streams** — Server-pushed DOM updates. Use for CRUD operations that need to update multiple page sections. Respond with `turbo_stream` format from controllers.
4. **Stimulus controller** — Client-side behavior that Turbo can't handle (toggles, form validation, UI interactions). Keep controllers thin.
5. **Direct JS** — Last resort. Only for complex map interactions, canvas rendering, or third-party library integration (Leaflet, MapLibre, Chartkick).

### Turbo Stream Responses

For CRUD actions (create, update, destroy), respond with Turbo Streams instead of redirects or JSON:

```ruby
# Controller
def create
  @area = current_user.areas.new(area_params)
  if @area.save
    respond_to do |format|
      format.turbo_stream
      format.html { redirect_to areas_path }
    end
  end
end

# app/views/areas/create.turbo_stream.erb
<%= turbo_stream.prepend "areas-list", partial: "areas/area", locals: { area: @area } %>
<%= stream_flash(:notice, "Area created successfully") %>
```

Use the `FlashStreamable` concern (included in controllers) to send flash messages via Turbo Streams:

```ruby
include FlashStreamable

# In turbo_stream responses:
stream_flash(:notice, "Success message")
stream_flash(:error, "Error message")
```

### Flash Messages

- **Server-side (Turbo Stream):** Use `stream_flash` from the `FlashStreamable` concern. This appends a flash partial to the `#flash-messages` container.
- **Client-side (Stimulus/JS):** Import `Flash` from `flash_controller.js` and call `Flash.show(type, message)`:
  ```javascript
  import Flash from "./flash_controller"
  Flash.show("notice", "Operation completed")
  Flash.show("error", "Something went wrong")
  ```
- **Never** use raw `alert()`, `console.log` for user-facing messages, or create ad-hoc notification DOM elements.

### Stimulus Controllers

- Location: `app/javascript/controllers/`
- Naming: `<name>_controller.js` maps to `data-controller="<name>"` in HTML
- Use `static targets` for DOM references, `static values` for data from HTML attributes
- Always clean up in `disconnect()` (event listeners, timers, subscriptions)
- Prefer `data-action` attributes in HTML over `addEventListener` in JS
- For forms, prefer `this.formTarget.requestSubmit()` over manual `fetch()` calls — this preserves Turbo form handling, CSRF tokens, and Turbo Stream responses

### File Uploads

Use the unified `upload` controller (`upload_controller.js`) for all file upload forms. Configure via `data-upload-*-value` attributes:

```erb
<%= form_with data: {
  controller: "upload",
  upload_url_value: rails_direct_uploads_url,
  upload_field_name_value: "import[files][]",
  upload_multiple_value: true,
  upload_target: "form"
} do |f| %>
```

### What NOT to Do

- **No `fetch()` for form submissions** — Use `form_with` with Turbo. If you need custom headers (API key), use Stimulus to submit the form via `requestSubmit()`.
- **No `document.getElementById()` for updates** — Use Turbo Frames/Streams to replace DOM sections server-side.
- **No `showFlashMessage()` or ad-hoc flash functions** — Use `Flash.show()` (client) or `stream_flash` (server).
- **No ActionCable subscriptions for CRUD updates** — Use Turbo Stream broadcasts from models/controllers instead.
- **No separate upload controllers per form** — Use the unified `upload` controller with value attributes for configuration.

### When Direct JS Is Acceptable

- **Map rendering**: Leaflet (Maps v1) and MapLibre GL JS (Maps v2) require imperative JS for layers, markers, and interactions.
- **Chart rendering**: Chartkick handles its own DOM.
- **Third-party integrations**: Libraries that don't have Hotwire adapters.
- **Complex client-side computation**: Haversine distance, coordinate transforms, etc.

Even in these cases, wrap the integration in a Stimulus controller and connect it to the DOM via `data-controller`.

## Important Notes for Development

1. **Location Data**: Always handle location data with appropriate precision and privacy considerations
2. **PostGIS**: Leverage PostGIS features for geographic calculations rather than Ruby-based solutions
2.1 **Coordinates**: Use `lonlat` column in `points` table for geographic calculations
3. **Background Jobs**: Use Sidekiq for any potentially long-running operations
4. **Testing**: Include both unit and integration tests for location-based features
5. **Performance**: Consider database indexes for geographic queries
6. **Security**: Never log or expose user location data inappropriately
7. **Migrations**: Put all migrations (schema and data) in `db/migrate/`, not `db/data/`. Data manipulation migrations use the same `ActiveRecord::Migration` class and should run in the standard migration sequence.
8. **Public Sharing**: When implementing features that interact with stats, consider public sharing access patterns:
   - Use `public_accessible?` method to check if a stat can be publicly accessed
   - Support UUID-based access in API endpoints when appropriate
   - Respect expiration settings and disable sharing when expired
   - Only expose minimal necessary data in public sharing contexts

### Route Drawing Implementation (Cr
Download .txt
gitextract_7c6y4j0y/

├── .app_version
├── .circleci/
│   └── config.yml
├── .devcontainer/
│   ├── Dockerfile
│   ├── devcontainer.json
│   └── docker-compose.yml
├── .dockerignore
├── .gitattributes
├── .github/
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE/
│   │   └── bug_report.md
│   ├── dependabot.yml
│   └── workflows/
│       ├── attach_compose.yml
│       ├── biome.yml
│       ├── build_and_push.yml
│       ├── ci.yml
│       ├── release_notifications.yml
│       └── rubocop.yml
├── .gitignore
├── .rspec
├── .rubocop.yml
├── .ruby-version
├── AGENTS.md
├── CHANGELOG.md
├── CLAUDE.md
├── CONTRIBUTING.md
├── DEVELOPMENT.md
├── Gemfile
├── LICENSE
├── Procfile
├── Procfile.dev
├── Procfile.production
├── Procfile.prometheus.dev
├── README.md
├── Rakefile
├── app/
│   ├── assets/
│   │   ├── builds/
│   │   │   ├── .keep
│   │   │   └── tailwind.css
│   │   ├── config/
│   │   │   └── manifest.js
│   │   ├── images/
│   │   │   ├── .keep
│   │   │   └── favicon/
│   │   │       └── browserconfig.xml.erb
│   │   └── stylesheets/
│   │       ├── actiontext.css
│   │       ├── application.css
│   │       ├── application.tailwind.css
│   │       ├── leaflet.control.layers.tree.css
│   │       ├── leaflet_theme.css
│   │       ├── maplibre-gl.css
│   │       ├── maps_maplibre.css
│   │       ├── maps_maplibre_panel.css
│   │       ├── maps_maplibre_replay.css
│   │       └── maps_maplibre_timeline_feed.css
│   ├── channels/
│   │   ├── application_cable/
│   │   │   ├── channel.rb
│   │   │   └── connection.rb
│   │   ├── family_locations_channel.rb
│   │   ├── imports_channel.rb
│   │   ├── notifications_channel.rb
│   │   ├── points_channel.rb
│   │   └── tracks_channel.rb
│   ├── controllers/
│   │   ├── api/
│   │   │   └── v1/
│   │   │       ├── areas_controller.rb
│   │   │       ├── countries/
│   │   │       │   ├── borders_controller.rb
│   │   │       │   └── visited_cities_controller.rb
│   │   │       ├── digests_controller.rb
│   │   │       ├── families/
│   │   │       │   └── locations_controller.rb
│   │   │       ├── health_controller.rb
│   │   │       ├── imports_controller.rb
│   │   │       ├── insights_controller.rb
│   │   │       ├── locations_controller.rb
│   │   │       ├── maps/
│   │   │       │   └── hexagons_controller.rb
│   │   │       ├── overland/
│   │   │       │   └── batches_controller.rb
│   │   │       ├── owntracks/
│   │   │       │   └── points_controller.rb
│   │   │       ├── photos_controller.rb
│   │   │       ├── places_controller.rb
│   │   │       ├── plan_controller.rb
│   │   │       ├── points/
│   │   │       │   └── tracked_months_controller.rb
│   │   │       ├── points_controller.rb
│   │   │       ├── settings_controller.rb
│   │   │       ├── stats_controller.rb
│   │   │       ├── subscriptions_controller.rb
│   │   │       ├── tags_controller.rb
│   │   │       ├── timeline_controller.rb
│   │   │       ├── tracks/
│   │   │       │   └── points_controller.rb
│   │   │       ├── tracks_controller.rb
│   │   │       ├── users_controller.rb
│   │   │       ├── visits/
│   │   │       │   └── possible_places_controller.rb
│   │   │       └── visits_controller.rb
│   │   ├── api_controller.rb
│   │   ├── application_controller.rb
│   │   ├── areas_controller.rb
│   │   ├── auth/
│   │   │   └── ios_controller.rb
│   │   ├── concerns/
│   │   │   ├── .keep
│   │   │   ├── flash_streamable.rb
│   │   │   ├── safe_timestamp_parser.rb
│   │   │   ├── sortable.rb
│   │   │   └── utm_trackable.rb
│   │   ├── exports_controller.rb
│   │   ├── families_controller.rb
│   │   ├── family/
│   │   │   ├── invitations_controller.rb
│   │   │   ├── location_requests_controller.rb
│   │   │   ├── location_sharing_controller.rb
│   │   │   └── memberships_controller.rb
│   │   ├── home_controller.rb
│   │   ├── imports_controller.rb
│   │   ├── insights_controller.rb
│   │   ├── map/
│   │   │   ├── leaflet_controller.rb
│   │   │   ├── maplibre_controller.rb
│   │   │   └── timeline_feeds_controller.rb
│   │   ├── metrics_controller.rb
│   │   ├── notifications_controller.rb
│   │   ├── places_controller.rb
│   │   ├── points_controller.rb
│   │   ├── settings/
│   │   │   ├── background_jobs_controller.rb
│   │   │   ├── general_controller.rb
│   │   │   ├── integrations_controller.rb
│   │   │   ├── maps_controller.rb
│   │   │   ├── onboardings_controller.rb
│   │   │   └── users_controller.rb
│   │   ├── settings_controller.rb
│   │   ├── shared/
│   │   │   ├── digests_controller.rb
│   │   │   └── stats_controller.rb
│   │   ├── stats_controller.rb
│   │   ├── tags_controller.rb
│   │   ├── trips_controller.rb
│   │   ├── users/
│   │   │   ├── digests_controller.rb
│   │   │   ├── omniauth_callbacks_controller.rb
│   │   │   ├── registrations_controller.rb
│   │   │   └── sessions_controller.rb
│   │   └── visits_controller.rb
│   ├── helpers/
│   │   ├── application_helper.rb
│   │   ├── country_flag_helper.rb
│   │   ├── datetime_formatting_helper.rb
│   │   ├── flash_helper.rb
│   │   ├── insights_helper.rb
│   │   ├── month_styling_helper.rb
│   │   ├── points_helper.rb
│   │   ├── stats_comparison_helper.rb
│   │   ├── stats_helper.rb
│   │   ├── tags_helper.rb
│   │   ├── trips_helper.rb
│   │   ├── user_helper.rb
│   │   └── users/
│   │       └── digests_helper.rb
│   ├── javascript/
│   │   ├── README.md
│   │   ├── application.js
│   │   ├── channels/
│   │   │   ├── consumer.js
│   │   │   ├── family_locations_channel.js
│   │   │   ├── imports_channel.js
│   │   │   ├── index.js
│   │   │   ├── notifications_channel.js
│   │   │   └── points_channel.js
│   │   ├── controllers/
│   │   │   ├── activity_heatmap_controller.js
│   │   │   ├── add_visit_controller.js
│   │   │   ├── application.js
│   │   │   ├── area_creation_v2_controller.js
│   │   │   ├── area_drawer_controller.js
│   │   │   ├── area_selector_controller.js
│   │   │   ├── base_controller.js
│   │   │   ├── checkbox_select_all_controller.js
│   │   │   ├── clipboard_controller.js
│   │   │   ├── color_picker_controller.js
│   │   │   ├── datetime_controller.js
│   │   │   ├── emoji_picker_controller.js
│   │   │   ├── family_members_controller.js
│   │   │   ├── family_navbar_indicator_controller.js
│   │   │   ├── flash_controller.js
│   │   │   ├── imports_controller.js
│   │   │   ├── index.js
│   │   │   ├── location_sharing_toggle_controller.js
│   │   │   ├── map_controls_controller.js
│   │   │   ├── map_panel_controller.js
│   │   │   ├── map_preview_controller.js
│   │   │   ├── maps/
│   │   │   │   ├── maplibre/
│   │   │   │   │   ├── area_selection_manager.js
│   │   │   │   │   ├── data_loader.js
│   │   │   │   │   ├── date_manager.js
│   │   │   │   │   ├── event_handlers.js
│   │   │   │   │   ├── filter_manager.js
│   │   │   │   │   ├── layer_manager.js
│   │   │   │   │   ├── map_data_manager.js
│   │   │   │   │   ├── map_initializer.js
│   │   │   │   │   ├── places_manager.js
│   │   │   │   │   ├── routes_manager.js
│   │   │   │   │   ├── settings_manager.js
│   │   │   │   │   └── visits_manager.js
│   │   │   │   ├── maplibre_controller.js
│   │   │   │   └── maplibre_realtime_controller.js
│   │   │   ├── maps_controller.js
│   │   │   ├── notifications_controller.js
│   │   │   ├── onboarding_modal_controller.js
│   │   │   ├── place_creation_controller.js
│   │   │   ├── places_filter_controller.js
│   │   │   ├── privacy_radius_controller.js
│   │   │   ├── public_stat_map_controller.js
│   │   │   ├── removals_controller.js
│   │   │   ├── sharing_modal_controller.js
│   │   │   ├── speed_color_editor_controller.js
│   │   │   ├── stat_page_controller.js
│   │   │   ├── timeline_feed_controller.js
│   │   │   ├── trip_map_controller.js
│   │   │   ├── trips_controller.js
│   │   │   ├── upload_controller.js
│   │   │   ├── visit_creation_v2_controller.js
│   │   │   ├── visit_modal_map_controller.js
│   │   │   ├── visit_modal_places_controller.js
│   │   │   ├── visit_name_controller.js
│   │   │   └── visits_map_controller.js
│   │   ├── maps/
│   │   │   ├── areas.js
│   │   │   ├── country_codes.js
│   │   │   ├── fog_of_war.js
│   │   │   ├── helpers.js
│   │   │   ├── layers.js
│   │   │   ├── live_map_handler.js
│   │   │   ├── location_search.js
│   │   │   ├── map_controls.js
│   │   │   ├── marker_factory.js
│   │   │   ├── markers.js
│   │   │   ├── photos.js
│   │   │   ├── places.js
│   │   │   ├── places_control.js
│   │   │   ├── polylines.js
│   │   │   ├── popups.js
│   │   │   ├── privacy_zones.js
│   │   │   ├── raster_maps_config.js
│   │   │   ├── scratch_layer.js
│   │   │   ├── theme_utils.js
│   │   │   ├── tracks.js
│   │   │   ├── vector_maps_config.js
│   │   │   └── visits.js
│   │   ├── maps_maplibre/
│   │   │   ├── channels/
│   │   │   │   └── map_channel.js
│   │   │   ├── components/
│   │   │   │   ├── photo_popup.js
│   │   │   │   ├── toast.js
│   │   │   │   ├── upgrade_banner.js
│   │   │   │   ├── visit_card.js
│   │   │   │   └── visit_popup.js
│   │   │   ├── layers/
│   │   │   │   ├── areas_layer.js
│   │   │   │   ├── base_layer.js
│   │   │   │   ├── family_layer.js
│   │   │   │   ├── fog_layer.js
│   │   │   │   ├── heatmap_layer.js
│   │   │   │   ├── photos_layer.js
│   │   │   │   ├── places_layer.js
│   │   │   │   ├── points_layer.js
│   │   │   │   ├── recent_point_layer.js
│   │   │   │   ├── replay_marker_layer.js
│   │   │   │   ├── routes_layer.js
│   │   │   │   ├── scratch_layer.js
│   │   │   │   ├── selected_points_layer.js
│   │   │   │   ├── selection_layer.js
│   │   │   │   ├── track_points_layer.js
│   │   │   │   ├── tracks_layer.js
│   │   │   │   └── visits_layer.js
│   │   │   ├── managers/
│   │   │   │   └── replay_manager.js
│   │   │   ├── services/
│   │   │   │   ├── api_client.js
│   │   │   │   └── location_search_service.js
│   │   │   └── utils/
│   │   │       ├── cleanup_helper.js
│   │   │       ├── fps_monitor.js
│   │   │       ├── geojson_transformers.js
│   │   │       ├── geometry.js
│   │   │       ├── layer_gate.js
│   │   │       ├── lazy_loader.js
│   │   │       ├── performance_monitor.js
│   │   │       ├── popup_theme.js
│   │   │       ├── progressive_loader.js
│   │   │       ├── route_segmenter.js
│   │   │       ├── search_manager.js
│   │   │       ├── settings_manager.js
│   │   │       ├── speed_colors.js
│   │   │       ├── style_manager.js
│   │   │       └── websocket_manager.js
│   │   ├── posthog.js
│   │   └── styles/
│   │       └── visits.css
│   ├── jobs/
│   │   ├── app_version_checking_job.rb
│   │   ├── application_job.rb
│   │   ├── area_visits_calculating_job.rb
│   │   ├── area_visits_calculation_scheduling_job.rb
│   │   ├── bulk_stats_calculating_job.rb
│   │   ├── bulk_visits_suggesting_job.rb
│   │   ├── cache/
│   │   │   ├── cleaning_job.rb
│   │   │   └── preheating_job.rb
│   │   ├── concerns/
│   │   │   └── user_timezone.rb
│   │   ├── data_migrations/
│   │   │   ├── backfill_country_name_job.rb
│   │   │   ├── backfill_motion_data_job.rb
│   │   │   ├── backfill_onboarding_completed_job.rb
│   │   │   ├── fix_route_opacity_job.rb
│   │   │   ├── migrate_places_lonlat_job.rb
│   │   │   ├── migrate_points_latlon_job.rb
│   │   │   ├── prefill_points_counter_cache_job.rb
│   │   │   ├── set_points_country_ids_job.rb
│   │   │   ├── set_reverse_geocoded_at_for_points_job.rb
│   │   │   └── start_settings_points_country_ids_job.rb
│   │   ├── enqueue_background_job.rb
│   │   ├── export_job.rb
│   │   ├── families/
│   │   │   └── expire_location_requests_job.rb
│   │   ├── family/
│   │   │   └── invitations/
│   │   │       ├── cleanup_job.rb
│   │   │       └── sending_job.rb
│   │   ├── import/
│   │   │   ├── google_takeout_job.rb
│   │   │   ├── immich_geodata_job.rb
│   │   │   ├── photoprism_geodata_job.rb
│   │   │   ├── process_job.rb
│   │   │   ├── update_points_count_job.rb
│   │   │   └── watcher_job.rb
│   │   ├── imports/
│   │   │   └── destroy_job.rb
│   │   ├── lite/
│   │   │   └── archival_warning_job.rb
│   │   ├── place_visits_calculating_job.rb
│   │   ├── places/
│   │   │   ├── bulk_name_fetching_job.rb
│   │   │   └── name_fetching_job.rb
│   │   ├── points/
│   │   │   ├── create_job.rb
│   │   │   ├── nightly_reverse_geocoding_job.rb
│   │   │   └── raw_data/
│   │   │       ├── archive_job.rb
│   │   │       └── re_archive_month_job.rb
│   │   ├── reverse_geocoding_job.rb
│   │   ├── stale_jobs_recovery_job.rb
│   │   ├── stats/
│   │   │   └── calculating_job.rb
│   │   ├── tracks/
│   │   │   ├── boundary_resolver_job.rb
│   │   │   ├── daily_generation_job.rb
│   │   │   ├── deduplication_job.rb
│   │   │   ├── parallel_generator_job.rb
│   │   │   ├── realtime_generation_job.rb
│   │   │   ├── recalculate_job.rb
│   │   │   ├── time_chunk_processor_job.rb
│   │   │   └── transportation_mode_recalculation_job.rb
│   │   ├── transportation_modes/
│   │   │   ├── backfill_job.rb
│   │   │   └── import_backfill_job.rb
│   │   ├── trips/
│   │   │   ├── calculate_all_job.rb
│   │   │   ├── calculate_countries_job.rb
│   │   │   ├── calculate_distance_job.rb
│   │   │   └── calculate_path_job.rb
│   │   ├── users/
│   │   │   ├── destroy_job.rb
│   │   │   ├── digests/
│   │   │   │   ├── calculating_job.rb
│   │   │   │   ├── email_sending_job.rb
│   │   │   │   └── year_end_scheduling_job.rb
│   │   │   ├── export_data_job.rb
│   │   │   ├── import_data_job.rb
│   │   │   ├── mailer_sending_job.rb
│   │   │   ├── recalculate_data_job.rb
│   │   │   └── trial_webhook_job.rb
│   │   └── visit_suggesting_job.rb
│   ├── mailers/
│   │   ├── application_mailer.rb
│   │   ├── family_mailer.rb
│   │   ├── users/
│   │   │   └── digests_mailer.rb
│   │   └── users_mailer.rb
│   ├── models/
│   │   ├── application_record.rb
│   │   ├── area.rb
│   │   ├── concerns/
│   │   │   ├── .keep
│   │   │   ├── archivable.rb
│   │   │   ├── calculateable.rb
│   │   │   ├── distance_convertible.rb
│   │   │   ├── distanceable.rb
│   │   │   ├── nearable.rb
│   │   │   ├── omniauthable.rb
│   │   │   ├── plan_scopable.rb
│   │   │   ├── point_validation.rb
│   │   │   ├── soft_deletable.rb
│   │   │   ├── taggable.rb
│   │   │   └── user_family.rb
│   │   ├── country.rb
│   │   ├── export.rb
│   │   ├── family/
│   │   │   ├── invitation.rb
│   │   │   ├── location_request.rb
│   │   │   └── membership.rb
│   │   ├── family.rb
│   │   ├── import.rb
│   │   ├── notification.rb
│   │   ├── place.rb
│   │   ├── place_visit.rb
│   │   ├── point.rb
│   │   ├── points/
│   │   │   └── raw_data_archive.rb
│   │   ├── stat.rb
│   │   ├── tag.rb
│   │   ├── tagging.rb
│   │   ├── track.rb
│   │   ├── track_segment.rb
│   │   ├── trip.rb
│   │   ├── user.rb
│   │   ├── users/
│   │   │   └── digest.rb
│   │   ├── visit.rb
│   │   └── visit_draft.rb
│   ├── policies/
│   │   ├── application_policy.rb
│   │   ├── family/
│   │   │   ├── invitation_policy.rb
│   │   │   └── membership_policy.rb
│   │   ├── family_policy.rb
│   │   ├── import_policy.rb
│   │   ├── insights_policy.rb
│   │   ├── place_policy.rb
│   │   └── tag_policy.rb
│   ├── queries/
│   │   ├── stats/
│   │   │   ├── daily_distance_query.rb
│   │   │   └── time_of_day_query.rb
│   │   ├── stats_query.rb
│   │   └── tracks/
│   │       └── index_query.rb
│   ├── serializers/
│   │   ├── api/
│   │   │   ├── digest_detail_serializer.rb
│   │   │   ├── digest_list_serializer.rb
│   │   │   ├── insights_details_serializer.rb
│   │   │   ├── insights_overview_serializer.rb
│   │   │   ├── location_search_result_serializer.rb
│   │   │   ├── photo_serializer.rb
│   │   │   ├── place_serializer.rb
│   │   │   ├── point_serializer.rb
│   │   │   ├── slim_point_serializer.rb
│   │   │   ├── user_serializer.rb
│   │   │   └── visit_serializer.rb
│   │   ├── export_serializer.rb
│   │   ├── exports/
│   │   │   ├── point_geojson_serializer.rb
│   │   │   └── point_gpx_serializer.rb
│   │   ├── point_serializer.rb
│   │   ├── points/
│   │   │   ├── geojson_serializer.rb
│   │   │   └── gpx_serializer.rb
│   │   ├── stats_serializer.rb
│   │   ├── tag_serializer.rb
│   │   ├── track_serializer.rb
│   │   ├── tracks/
│   │   │   └── geojson_serializer.rb
│   │   └── tracks_serializer.rb
│   ├── services/
│   │   ├── areas/
│   │   │   └── visits/
│   │   │       └── create.rb
│   │   ├── cache/
│   │   │   ├── clean.rb
│   │   │   ├── invalidate_user_caches.rb
│   │   │   └── preheat_insights_digests.rb
│   │   ├── check_app_version.rb
│   │   ├── concerns/
│   │   │   └── ssl_configurable.rb
│   │   ├── countries/
│   │   │   └── iso_code_mapper.rb
│   │   ├── countries_and_cities.rb
│   │   ├── exception_reporter.rb
│   │   ├── exports/
│   │   │   └── create.rb
│   │   ├── families/
│   │   │   ├── accept_invitation.rb
│   │   │   ├── create.rb
│   │   │   ├── create_location_request.rb
│   │   │   ├── invite.rb
│   │   │   ├── locations.rb
│   │   │   ├── memberships/
│   │   │   │   └── destroy.rb
│   │   │   └── update_location_sharing.rb
│   │   ├── geojson/
│   │   │   ├── importer.rb
│   │   │   └── params.rb
│   │   ├── google_maps/
│   │   │   ├── phone_takeout_importer.rb
│   │   │   ├── records_importer.rb
│   │   │   ├── records_storage_importer.rb
│   │   │   └── semantic_history_importer.rb
│   │   ├── gpx/
│   │   │   └── track_importer.rb
│   │   ├── immich/
│   │   │   ├── connection_tester.rb
│   │   │   ├── import_geodata.rb
│   │   │   ├── request_photos.rb
│   │   │   ├── response_analyzer.rb
│   │   │   └── response_validator.rb
│   │   ├── imports/
│   │   │   ├── broadcaster.rb
│   │   │   ├── create.rb
│   │   │   ├── destroy.rb
│   │   │   ├── file_loader.rb
│   │   │   ├── secure_file_downloader.rb
│   │   │   ├── source_detector.rb
│   │   │   └── watcher.rb
│   │   ├── insights/
│   │   │   ├── activity_heatmap_calculator.rb
│   │   │   ├── travel_insight_generator.rb
│   │   │   ├── travel_patterns_loader.rb
│   │   │   ├── year_comparison_calculator.rb
│   │   │   └── year_totals_calculator.rb
│   │   ├── jobs/
│   │   │   └── create.rb
│   │   ├── kml/
│   │   │   └── importer.rb
│   │   ├── location_search/
│   │   │   ├── geocoding_service.rb
│   │   │   ├── point_finder.rb
│   │   │   ├── result_aggregator.rb
│   │   │   └── spatial_matcher.rb
│   │   ├── maps/
│   │   │   ├── bounds_calculator.rb
│   │   │   ├── hexagon_center_manager.rb
│   │   │   ├── hexagon_polygon_generator.rb
│   │   │   └── hexagon_request_handler.rb
│   │   ├── metrics/
│   │   │   └── archives/
│   │   │       ├── compression_ratio.rb
│   │   │       ├── count_mismatch.rb
│   │   │       ├── operation.rb
│   │   │       ├── points_archived.rb
│   │   │       ├── size.rb
│   │   │       └── verification.rb
│   │   ├── notifications.rb
│   │   ├── overland/
│   │   │   ├── params.rb
│   │   │   └── points_creator.rb
│   │   ├── own_tracks/
│   │   │   ├── importer.rb
│   │   │   ├── params.rb
│   │   │   ├── point_creator.rb
│   │   │   └── rec_parser.rb
│   │   ├── photoprism/
│   │   │   ├── cache_preview_token.rb
│   │   │   ├── connection_tester.rb
│   │   │   ├── import_geodata.rb
│   │   │   ├── request_photos.rb
│   │   │   └── response_validator.rb
│   │   ├── photos/
│   │   │   ├── cache_cleaner.rb
│   │   │   ├── importer.rb
│   │   │   ├── search.rb
│   │   │   └── thumbnail.rb
│   │   ├── places/
│   │   │   ├── name_fetcher.rb
│   │   │   ├── nearby_search.rb
│   │   │   └── visits/
│   │   │       └── create.rb
│   │   ├── points/
│   │   │   ├── create.rb
│   │   │   ├── live_broadcaster.rb
│   │   │   ├── motion_data_extractor.rb
│   │   │   ├── params.rb
│   │   │   ├── raw_data/
│   │   │   │   ├── archiver.rb
│   │   │   │   ├── chunk_compressor.rb
│   │   │   │   ├── clearer.rb
│   │   │   │   ├── encryption.rb
│   │   │   │   ├── restorer.rb
│   │   │   │   └── verifier.rb
│   │   │   └── raw_data_lonlat_extractor.rb
│   │   ├── points_limit_exceeded.rb
│   │   ├── prometheus_metrics.rb
│   │   ├── reverse_geocoding/
│   │   │   ├── places/
│   │   │   │   └── fetch_data.rb
│   │   │   └── points/
│   │   │       └── fetch_data.rb
│   │   ├── settings/
│   │   │   └── update.rb
│   │   ├── stats/
│   │   │   ├── bulk_calculator.rb
│   │   │   ├── calculate_month.rb
│   │   │   └── hexagon_calculator.rb
│   │   ├── subscription/
│   │   │   ├── decode_jwt_token.rb
│   │   │   └── encode_jwt_token.rb
│   │   ├── supporter/
│   │   │   └── verify_email.rb
│   │   ├── tasks/
│   │   │   └── imports/
│   │   │       └── google_records.rb
│   │   ├── timeline/
│   │   │   └── day_assembler.rb
│   │   ├── tracks/
│   │   │   ├── boundary_detector.rb
│   │   │   ├── build_path.rb
│   │   │   ├── deduplicator.rb
│   │   │   ├── incremental_generator.rb
│   │   │   ├── merger.rb
│   │   │   ├── parallel_generator.rb
│   │   │   ├── realtime_debouncer.rb
│   │   │   ├── reprocessor.rb
│   │   │   ├── segmentation.rb
│   │   │   ├── session_manager.rb
│   │   │   ├── time_chunker.rb
│   │   │   ├── track_builder.rb
│   │   │   └── transportation_recalculation_status.rb
│   │   ├── transportation_modes/
│   │   │   ├── activity_backfiller.rb
│   │   │   ├── detector.rb
│   │   │   ├── mode_classifier.rb
│   │   │   ├── movement_analyzer.rb
│   │   │   └── source_data_extractor.rb
│   │   ├── trips/
│   │   │   └── photos.rb
│   │   ├── users/
│   │   │   ├── destroy.rb
│   │   │   ├── digests/
│   │   │   │   ├── activity_breakdown_calculator.rb
│   │   │   │   ├── calculate_month.rb
│   │   │   │   ├── calculate_year.rb
│   │   │   │   ├── first_time_visits_calculator.rb
│   │   │   │   ├── month_over_month_calculator.rb
│   │   │   │   ├── monthly_first_time_visits_calculator.rb
│   │   │   │   ├── seasonality_calculator.rb
│   │   │   │   └── year_over_year_calculator.rb
│   │   │   ├── export_data/
│   │   │   │   ├── areas.rb
│   │   │   │   ├── digests.rb
│   │   │   │   ├── exports.rb
│   │   │   │   ├── imports.rb
│   │   │   │   ├── notifications.rb
│   │   │   │   ├── places.rb
│   │   │   │   ├── points.rb
│   │   │   │   ├── stats.rb
│   │   │   │   ├── tracks.rb
│   │   │   │   ├── trips.rb
│   │   │   │   └── visits.rb
│   │   │   ├── export_data.rb
│   │   │   ├── import_data/
│   │   │   │   ├── areas.rb
│   │   │   │   ├── digests.rb
│   │   │   │   ├── exports.rb
│   │   │   │   ├── imports.rb
│   │   │   │   ├── notifications.rb
│   │   │   │   ├── places.rb
│   │   │   │   ├── points.rb
│   │   │   │   ├── raw_data_archives.rb
│   │   │   │   ├── settings.rb
│   │   │   │   ├── stats.rb
│   │   │   │   ├── taggings.rb
│   │   │   │   ├── tags.rb
│   │   │   │   ├── tracks.rb
│   │   │   │   ├── trips.rb
│   │   │   │   ├── v1_handler.rb
│   │   │   │   ├── v2_handler.rb
│   │   │   │   └── visits.rb
│   │   │   ├── import_data.rb
│   │   │   ├── safe_settings.rb
│   │   │   └── transportation_thresholds_updater.rb
│   │   └── visits/
│   │       ├── bulk_update.rb
│   │       ├── create.rb
│   │       ├── creator.rb
│   │       ├── detector.rb
│   │       ├── find_in_time.rb
│   │       ├── find_within_bounding_box.rb
│   │       ├── finder.rb
│   │       ├── group.rb
│   │       ├── merge_service.rb
│   │       ├── merger.rb
│   │       ├── names/
│   │       │   ├── builder.rb
│   │       │   ├── fetcher.rb
│   │       │   └── suggester.rb
│   │       ├── place_finder.rb
│   │       ├── smart_detect.rb
│   │       ├── suggest.rb
│   │       └── time_chunks.rb
│   └── views/
│       ├── active_storage/
│       │   └── blobs/
│       │       └── _blob.html.erb
│       ├── application/
│       │   └── _favicon.html.erb
│       ├── devise/
│       │   ├── confirmations/
│       │   │   └── new.html.erb
│       │   ├── mailer/
│       │   │   ├── confirmation_instructions.html.erb
│       │   │   ├── email_changed.html.erb
│       │   │   ├── password_change.html.erb
│       │   │   ├── reset_password_instructions.html.erb
│       │   │   └── unlock_instructions.html.erb
│       │   ├── passwords/
│       │   │   ├── edit.html.erb
│       │   │   └── new.html.erb
│       │   ├── registrations/
│       │   │   ├── _api_key.html.erb
│       │   │   ├── _points_usage.html.erb
│       │   │   ├── edit.html.erb
│       │   │   └── new.html.erb
│       │   ├── sessions/
│       │   │   └── new.html.erb
│       │   ├── shared/
│       │   │   ├── _error_messages.html.erb
│       │   │   └── _links.html.erb
│       │   └── unlocks/
│       │       └── new.html.erb
│       ├── exports/
│       │   ├── _table_row.html.erb
│       │   └── index.html.erb
│       ├── families/
│       │   ├── _location_sharing_toggle.html.erb
│       │   ├── _navbar_indicator.html.erb
│       │   ├── edit.html.erb
│       │   ├── index.html.erb
│       │   ├── new.html.erb
│       │   └── show.html.erb
│       ├── family/
│       │   ├── invitations/
│       │   │   ├── index.html.erb
│       │   │   └── show.html.erb
│       │   └── location_requests/
│       │       └── show.html.erb
│       ├── family_mailer/
│       │   ├── invitation.html.erb
│       │   ├── invitation.text.erb
│       │   ├── location_request.html.erb
│       │   ├── location_request.text.erb
│       │   ├── member_joined.html.erb
│       │   └── member_joined.text.erb
│       ├── home/
│       │   └── index.html.erb
│       ├── imports/
│       │   ├── _form.html.erb
│       │   ├── _import.html.erb
│       │   ├── _table_row.html.erb
│       │   ├── destroy.turbo_stream.erb
│       │   ├── edit.html.erb
│       │   ├── index.html.erb
│       │   ├── new.html.erb
│       │   └── show.html.erb
│       ├── insights/
│       │   ├── _activity_breakdown.html.erb
│       │   ├── _activity_heatmap.html.erb
│       │   ├── _activity_heatmap_cells.html.erb
│       │   ├── _activity_streak.html.erb
│       │   ├── _details_skeleton.html.erb
│       │   ├── _header.html.erb
│       │   ├── _location_clusters.html.erb
│       │   ├── _monthly_digest.html.erb
│       │   ├── _movement_wellness.html.erb
│       │   ├── _pro_locked_card.html.erb
│       │   ├── _stats_row.html.erb
│       │   ├── _top_visited_locations.html.erb
│       │   ├── _travel_patterns.html.erb
│       │   ├── _travel_story.html.erb
│       │   ├── _year_comparison.html.erb
│       │   ├── details.html.erb
│       │   └── index.html.erb
│       ├── kaminari/
│       │   ├── _first_page.html.erb
│       │   ├── _gap.html.erb
│       │   ├── _last_page.html.erb
│       │   ├── _next_page.html.erb
│       │   ├── _page.html.erb
│       │   ├── _paginator.html.erb
│       │   └── _prev_page.html.erb
│       ├── layouts/
│       │   ├── action_text/
│       │   │   └── contents/
│       │   │       └── _content.html.erb
│       │   ├── application.html.erb
│       │   ├── mailer.html.erb
│       │   ├── mailer.text.erb
│       │   └── map.html.erb
│       ├── map/
│       │   ├── _onboarding_modal.html.erb
│       │   ├── leaflet/
│       │   │   ├── _settings_modals.html.erb
│       │   │   └── index.html.erb
│       │   ├── maplibre/
│       │   │   ├── _area_creation_modal.html.erb
│       │   │   ├── _replay_panel.html.erb
│       │   │   ├── _settings_panel.html.erb
│       │   │   ├── _visit_creation_modal.html.erb
│       │   │   ├── _webgl_error.html.erb
│       │   │   └── index.html.erb
│       │   └── timeline_feeds/
│       │       ├── _day.html.erb
│       │       ├── _day_summary.html.erb
│       │       ├── _feed.html.erb
│       │       ├── _journey_entry.html.erb
│       │       ├── _track_info.html.erb
│       │       ├── _visit_entry.html.erb
│       │       ├── index.html.erb
│       │       └── track_info.html.erb
│       ├── notifications/
│       │   ├── _badge.html.erb
│       │   ├── _navbar_item.html.erb
│       │   ├── _notification.html.erb
│       │   ├── index.html.erb
│       │   └── show.html.erb
│       ├── places/
│       │   ├── _nearby_places.html.erb
│       │   └── index.html.erb
│       ├── points/
│       │   ├── _point.html.erb
│       │   └── index.html.erb
│       ├── settings/
│       │   ├── _navigation.html.erb
│       │   ├── background_jobs/
│       │   │   └── index.html.erb
│       │   ├── general/
│       │   │   ├── _supporter_status.html.erb
│       │   │   └── index.html.erb
│       │   ├── integrations/
│       │   │   └── index.html.erb
│       │   ├── maps/
│       │   │   └── index.html.erb
│       │   ├── subscriptions/
│       │   │   └── index.html.erb
│       │   └── users/
│       │       ├── edit.html.erb
│       │       ├── index.html.erb
│       │       └── show.html.erb
│       ├── shared/
│       │   ├── _chartkick_scripts.html.erb
│       │   ├── _flash.html.erb
│       │   ├── _flash_message.html.erb
│       │   ├── _footer.html.erb
│       │   ├── _legal_footer.html.erb
│       │   ├── _navbar.html.erb
│       │   ├── _place_creation_modal.html.erb
│       │   ├── _plan_data_window_alert.html.erb
│       │   ├── _sharing_link.html.erb
│       │   ├── _sharing_modal.html.erb
│       │   ├── _trix_scripts.html.erb
│       │   ├── map/
│       │   │   ├── _date_navigation.html.erb
│       │   │   ├── _date_navigation_v2.html.erb
│       │   │   └── _upgrade_banner.html.erb
│       │   └── navbar/
│       │       ├── _help_links.html.erb
│       │       └── _theme_toggle.html.erb
│       ├── stats/
│       │   ├── _locked_year_card.html.erb
│       │   ├── _month.html.erb
│       │   ├── _reverse_geocoding_stats.html.erb
│       │   ├── _stat.html.erb
│       │   ├── _year.html.erb
│       │   ├── index.html.erb
│       │   ├── month.html.erb
│       │   ├── public_month.html.erb
│       │   └── show.html.erb
│       ├── tags/
│       │   ├── _form.html.erb
│       │   ├── edit.html.erb
│       │   ├── index.html.erb
│       │   └── new.html.erb
│       ├── trips/
│       │   ├── _countries.html.erb
│       │   ├── _distance.html.erb
│       │   ├── _form.html.erb
│       │   ├── _path.html.erb
│       │   ├── _trip.html.erb
│       │   ├── edit.html.erb
│       │   ├── index.html.erb
│       │   ├── new.html.erb
│       │   └── show.html.erb
│       ├── users/
│       │   ├── digests/
│       │   │   ├── index.html.erb
│       │   │   ├── public_year.html.erb
│       │   │   └── show.html.erb
│       │   └── digests_mailer/
│       │       ├── year_end_digest.html.erb
│       │       └── year_end_digest.text.erb
│       ├── users_mailer/
│       │   ├── archival_approaching.html.erb
│       │   ├── archival_approaching.text.erb
│       │   ├── explore_features.html.erb
│       │   ├── explore_features.text.erb
│       │   ├── post_trial_reminder_early.html.erb
│       │   ├── post_trial_reminder_early.text.erb
│       │   ├── post_trial_reminder_late.html.erb
│       │   ├── post_trial_reminder_late.text.erb
│       │   ├── trial_expired.html.erb
│       │   ├── trial_expired.text.erb
│       │   ├── trial_expires_soon.html.erb
│       │   ├── trial_expires_soon.text.erb
│       │   ├── welcome.html.erb
│       │   └── welcome.text.erb
│       └── visits/
│           ├── _buttons.html.erb
│           ├── _modal.html.erb
│           ├── _name.html.erb
│           ├── _visit.html.erb
│           └── index.html.erb
├── app.json
├── bin/
│   ├── dev
│   ├── importmap
│   ├── rails
│   ├── rake
│   ├── rubocop
│   └── setup
├── biome.json
├── config/
│   ├── application.rb
│   ├── boot.rb
│   ├── cable.yml
│   ├── database.ci.yml
│   ├── database.yml
│   ├── environment.rb
│   ├── environments/
│   │   ├── development.rb
│   │   ├── production.rb
│   │   ├── staging.rb
│   │   └── test.rb
│   ├── favicon.json
│   ├── importmap.rb
│   ├── initializers/
│   │   ├── 00_random.rb
│   │   ├── 01_constants.rb
│   │   ├── 03_dawarich_settings.rb
│   │   ├── assets.rb
│   │   ├── aws.rb
│   │   ├── cache_jobs.rb
│   │   ├── content_security_policy.rb
│   │   ├── devise.rb
│   │   ├── dns_cache.rb
│   │   ├── filter_parameter_logging.rb
│   │   ├── geocoder.rb
│   │   ├── httparty.rb
│   │   ├── inflections.rb
│   │   ├── mime_types.rb
│   │   ├── new_framework_defaults_8_0.rb
│   │   ├── oj.rb
│   │   ├── permissions_policy.rb
│   │   ├── prometheus.rb
│   │   ├── rack_attack.rb
│   │   ├── rails_icons.rb
│   │   ├── rails_pulse.rb
│   │   ├── rswag_api.rb
│   │   ├── rswag_ui.rb
│   │   ├── sentry.rb
│   │   ├── sidekiq.rb
│   │   └── web_app_manifest.rb
│   ├── locales/
│   │   ├── devise.en.yml
│   │   └── en.yml
│   ├── puma.rb
│   ├── routes.rb
│   ├── schedule.yml
│   ├── sidekiq.yml
│   ├── storage.yml
│   └── tailwind.config.js
├── config.ru
├── db/
│   ├── data/
│   │   ├── 20240525110530_bind_existing_points_to_first_user.rb
│   │   ├── 20240610170930_remove_points_without_coordinates.rb
│   │   ├── 20240625201842_add_fog_of_war_meters_to_settings.rb
│   │   ├── 20240713103122_make_first_user_admin.rb
│   │   ├── 20240724141417_add_visit_settings_to_user.rb
│   │   ├── 20240730130922_add_route_opacity_to_settings.rb
│   │   ├── 20240808133112_run_initial_visit_suggestion.rb
│   │   ├── 20240815174852_add_owntracks_points_data.rb
│   │   ├── 20240822094532_add_counter_cache_to_imports.rb
│   │   ├── 20241022100309_add_points_rendering_mode_to_settings.rb
│   │   ├── 20241107112451_add_live_map_enabled_to_settings.rb
│   │   ├── 20241202125248_set_reverse_geocoded_at_for_points.rb
│   │   ├── 20241206163450_create_telemetry_notification.rb
│   │   ├── 20250104204852_create_photon_load_notification.rb
│   │   ├── 20250120154554_remove_duplicate_points.rb
│   │   ├── 20250123151849_create_paths_for_trips.rb
│   │   ├── 20250222213848_migrate_points_latlon.rb
│   │   ├── 20250226192005_activate_selfhosted_users.rb
│   │   ├── 20250303194123_migrate_places_lonlat.rb
│   │   ├── 20250403204658_update_imports_points_count.rb
│   │   ├── 20250404182629_set_active_until_for_selfhosted_users.rb
│   │   ├── 20250516180933_set_points_country_ids.rb
│   │   ├── 20250518173936_fix_france_codes.rb
│   │   ├── 20250518174305_set_default_distance_unit_for_user.rb
│   │   ├── 20250704185707_create_tracks_from_points.rb
│   │   ├── 20250709195003_recalculate_trips_distance.rb
│   │   └── 20250720171241_recalculate_stats_after_changing_distance_units.rb
│   ├── data_schema.rb
│   ├── migrate/
│   │   ├── 20220325100310_devise_create_users.rb
│   │   ├── 20231021104256_add_service_name_to_active_storage_blobs.active_storage.rb
│   │   ├── 20231021104257_create_active_storage_variant_records.active_storage.rb
│   │   ├── 20231021104258_remove_not_null_on_active_storage_blobs_checksum.active_storage.rb
│   │   ├── 20240315213523_create_points.rb
│   │   ├── 20240315215423_create_imports.rb
│   │   ├── 20240317171559_add_indicies_to_points_latitude_longitude.rb
│   │   ├── 20240323125126_add_raw_points_and_doubles_to_import.rb
│   │   ├── 20240323160300_create_stats.rb
│   │   ├── 20240323161049_add_index_to_points_timestamp.rb
│   │   ├── 20240323190039_add_user_id_to_stat.rb
│   │   ├── 20240324161309_create_active_storage_tables.active_storage.rb
│   │   ├── 20240324161800_add_processed_to_imports.rb
│   │   ├── 20240324173315_add_daily_distance_to_stat.rb
│   │   ├── 20240404154959_add_api_key_to_users.rb
│   │   ├── 20240425200155_add_raw_data_to_imports.rb
│   │   ├── 20240518095848_add_theme_to_users.rb
│   │   ├── 20240525110244_add_user_id_to_points.rb
│   │   ├── 20240612152451_create_exports.rb
│   │   ├── 20240620205120_add_settings_to_users.rb
│   │   ├── 20240630093005_add_fog_of_war_to_default_settings.rb
│   │   ├── 20240703105734_create_notifications.rb
│   │   ├── 20240712141303_add_geodata_to_points.rb
│   │   ├── 20240713103051_add_admin_to_users.rb
│   │   ├── 20240721165313_create_areas.rb
│   │   ├── 20240721183005_create_visits.rb
│   │   ├── 20240721183116_add_visit_id_to_points.rb
│   │   ├── 20240805150111_create_places.rb
│   │   ├── 20240808102348_add_place_id_to_visits.rb
│   │   ├── 20240808102425_make_area_id_optional_in_visits.rb
│   │   ├── 20240808121027_create_place_visits.rb
│   │   ├── 20240822092405_add_points_count_to_imports.rb
│   │   ├── 20241127161621_create_trips.rb
│   │   ├── 20241128095325_create_action_text_tables.action_text.rb
│   │   ├── 20241202114820_add_reverse_geocoded_at_to_points.rb
│   │   ├── 20241205160055_add_devise_trackable_columns_to_users.rb
│   │   ├── 20241211113119_add_started_at_index_to_visits.rb
│   │   ├── 20241226202204_add_database_users_constraints.rb
│   │   ├── 20241226202831_validate_add_database_users_constraints.rb
│   │   ├── 20250120152014_add_course_and_course_accuracy_to_points.rb
│   │   ├── 20250120152540_add_external_track_id_to_points.rb
│   │   ├── 20250120154555_add_unique_index_to_points.rb
│   │   ├── 20250123145155_enable_postgis_extension.rb
│   │   ├── 20250123151657_add_path_to_trips.rb
│   │   ├── 20250219195822_add_status_to_users.rb
│   │   ├── 20250221181805_add_lonlat_to_points.rb
│   │   ├── 20250221185032_add_lonlat_index.rb
│   │   ├── 20250221194430_remove_points_latitude_longitude_uniqueness_index.rb
│   │   ├── 20250221194509_add_unique_lon_lat_index_to_points.rb
│   │   ├── 20250303194009_add_lonlat_to_places.rb
│   │   ├── 20250303194043_add_lonlat_index_to_places.rb
│   │   ├── 20250324180755_add_format_start_at_end_at_to_exports.rb
│   │   ├── 20250404182437_add_active_until_to_users.rb
│   │   ├── 20250513164521_add_visited_countries_to_trips.rb
│   │   ├── 20250515190752_create_countries.rb
│   │   ├── 20250515192211_add_country_id_to_points.rb
│   │   ├── 20250625185030_add_file_type_to_exports.rb
│   │   ├── 20250627184017_add_status_to_imports.rb
│   │   ├── 20250703193656_create_tracks.rb
│   │   ├── 20250703193657_add_track_id_to_points.rb
│   │   ├── 20250721204404_add_index_on_places_geodata_osm_id.rb
│   │   ├── 20250723164055_add_track_generation_composite_index.rb
│   │   ├── 20250728191359_add_country_name_to_points.rb
│   │   ├── 20250821192219_add_points_count_to_users.rb
│   │   ├── 20250823125940_remove_default_from_imports_source.rb
│   │   ├── 20250905120121_add_user_country_composite_index_to_points.rb
│   │   ├── 20250910224538_add_sharing_fields_to_stats.rb
│   │   ├── 20250910224714_add_index_to_stats_share_uuid.rb
│   │   ├── 20250918215512_add_h3_hex_ids_to_stats.rb
│   │   ├── 20250926220114_create_families.rb
│   │   ├── 20250926220135_create_family_memberships.rb
│   │   ├── 20250926220158_create_family_invitations.rb
│   │   ├── 20250926220345_validate_family_foreign_keys.rb
│   │   ├── 20251028130433_add_omniauth_to_users.rb
│   │   ├── 20251030190924_add_utm_parameters_to_users.rb
│   │   ├── 20251116184506_add_user_id_to_places.rb
│   │   ├── 20251116184514_create_tags.rb
│   │   ├── 20251116184520_create_taggings.rb
│   │   ├── 20251118204141_add_privacy_radius_to_tags.rb
│   │   ├── 20251118210506_add_note_to_places.rb
│   │   ├── 20251201192510_add_user_id_reverse_geocoded_at_index_to_points.rb
│   │   ├── 20251206000001_create_points_raw_data_archives.rb
│   │   ├── 20251206000002_add_archival_columns_to_points.rb
│   │   ├── 20251206000004_validate_archival_foreign_keys.rb
│   │   ├── 20251208210410_add_composite_index_to_stats.rb
│   │   ├── 20251210193532_add_verified_at_to_points_raw_data_archives.rb
│   │   ├── 20251226170919_add_composite_index_to_points_user_id_timestamp.rb
│   │   ├── 20251227000001_create_digests.rb
│   │   ├── 20251227223614_change_digests_distance_to_bigint.rb
│   │   ├── 20251228000000_remove_unused_indexes.rb
│   │   ├── 20251228100000_add_performance_indexes.rb
│   │   ├── 20251228163703_install_rails_pulse_tables.rb
│   │   ├── 20260103114630_add_indexes_to_points_for_stats_query.rb
│   │   ├── 20260108192905_add_deleted_at_to_users.rb
│   │   ├── 20260112192240_set_existing_users_to_map_v1.rb
│   │   ├── 20260113230537_set_points_timestamp_from_geojson_date.rb
│   │   ├── 20260120193124_add_month_to_digests.rb
│   │   ├── 20260120193200_create_track_segments.rb
│   │   ├── 20260120193336_add_dominant_mode_to_tracks.rb
│   │   ├── 20260120193401_add_travel_patterns_to_digests.rb
│   │   ├── 20260120193501_change_tracks_distance_precision.rb
│   │   ├── 20260124221434_add_index_to_track_segments.rb
│   │   ├── 20260125100000_enqueue_transportation_mode_backfill_jobs.rb
│   │   ├── 20260201000001_add_processing_started_at_to_exports_and_imports.rb
│   │   ├── 20260201000002_add_error_message_to_exports.rb
│   │   ├── 20260206202634_deduplicate_tracks.rb
│   │   ├── 20260216190000_add_unique_index_to_raw_data_archives.rb
│   │   ├── 20260217000000_optimize_points_indexes.rb
│   │   ├── 20260217000001_backfill_motion_data_from_raw_data.rb
│   │   ├── 20260222215414_add_error_message_to_imports.rb
│   │   ├── 20260301201446_add_plan_to_users.rb
│   │   ├── 20260301202147_set_plan_for_existing_users.rb
│   │   ├── 20260310000001_drop_redundant_indexes.rb
│   │   ├── 20260310000002_add_composite_indexes_and_drop_low_selectivity.rb
│   │   ├── 20260310000003_add_unique_index_to_place_visits.rb
│   │   ├── 20260310000006_fix_tracks_original_path_srid.rb
│   │   ├── 20260313134546_create_family_location_requests.rb
│   │   ├── 20260314000001_fix_route_opacity_default.rb
│   │   └── 20260315000001_backfill_onboarding_completed_for_existing_users.rb
│   ├── rails_pulse_migrate/
│   │   └── .keep
│   ├── rails_pulse_schema.rb
│   ├── schema.rb
│   └── seeds.rb
├── docker/
│   ├── Dockerfile
│   ├── docker-compose.yml
│   ├── sidekiq-entrypoint.sh
│   └── web-entrypoint.sh
├── docs/
│   ├── How_to_extract_geodata_from_photos.md
│   ├── How_to_install_Dawarich_in_k8s.md
│   ├── How_to_install_Dawarich_on_Synology.md
│   ├── How_to_install_Dawarich_using_Docker.md
│   ├── how_to_setup_reverse_proxy.md
│   └── synology/
│       ├── docker-compose.yml
│       ├── spk.tgz
│       └── update.sh
├── e2e/
│   ├── README.md
│   ├── helpers/
│   │   ├── map.js
│   │   ├── navigation.js
│   │   ├── places.js
│   │   └── selection.js
│   ├── lite/
│   │   └── plan-gates.spec.js
│   ├── map/
│   │   ├── map-add-visit.spec.js
│   │   ├── map-bulk-delete.spec.js
│   │   ├── map-calendar-panel.spec.js
│   │   ├── map-controls.spec.js
│   │   ├── map-info-toggle.spec.js
│   │   ├── map-layers.spec.js
│   │   ├── map-places-creation.spec.js
│   │   ├── map-places-layers.spec.js
│   │   ├── map-points.spec.js
│   │   ├── map-route-interactions.spec.js
│   │   ├── map-routes-tracks-selector.spec.js
│   │   ├── map-search.spec.js
│   │   ├── map-selection-tool.spec.js
│   │   ├── map-settings-panel.spec.js
│   │   ├── map-side-panel.spec.js
│   │   ├── map-stats-display.spec.js
│   │   ├── map-suggested-visits.spec.js
│   │   └── map-visits.spec.js
│   ├── setup/
│   │   ├── auth-lite.setup.js
│   │   └── auth.setup.js
│   └── v2/
│       ├── helpers/
│       │   ├── api.js
│       │   ├── constants.js
│       │   └── setup.js
│       ├── map/
│       │   ├── area-selection.spec.js
│       │   ├── core.spec.js
│       │   ├── interactions.spec.js
│       │   ├── layers/
│       │   │   ├── advanced.spec.js
│       │   │   ├── areas.spec.js
│       │   │   ├── family.spec.js
│       │   │   ├── heatmap.spec.js
│       │   │   ├── photos.spec.js
│       │   │   ├── places.spec.js
│       │   │   ├── points.spec.js
│       │   │   ├── routes.spec.js
│       │   │   ├── track-segments.spec.js
│       │   │   ├── tracks.spec.js
│       │   │   └── visits.spec.js
│       │   ├── navigation.spec.js
│       │   ├── performance.spec.js
│       │   ├── replay.spec.js
│       │   ├── search.spec.js
│       │   ├── settings.spec.js
│       │   └── timeline-feed.spec.js
│       ├── realtime/
│       │   ├── family.spec.js
│       │   ├── live-mode-api.spec.js
│       │   └── live-mode.spec.js
│       └── trips.spec.js
├── lib/
│   ├── assets/
│   │   ├── .keep
│   │   └── countries.geojson
│   ├── json_stream_handler.rb
│   ├── tasks/
│   │   ├── .keep
│   │   ├── data_cleanup.rake
│   │   ├── demo.rake
│   │   ├── exports.rake
│   │   ├── import.rake
│   │   ├── imports.rake
│   │   ├── points.rake
│   │   ├── points_raw_data.rake
│   │   ├── rswag.rake
│   │   ├── users.rake
│   │   └── webmanifest.rake
│   └── timestamps.rb
├── log/
│   └── .keep
├── package.json
├── playwright.config.js
├── public/
│   ├── .well-known/
│   │   └── apple-app-site-association
│   ├── 400.html
│   ├── 404.html
│   ├── 406-unsupported-browser.html
│   ├── 422.html
│   ├── 500.html
│   ├── exports/
│   │   └── .keep
│   ├── maps_maplibre/
│   │   └── styles/
│   │       ├── black.json
│   │       ├── dark.json
│   │       ├── grayscale.json
│   │       ├── light.json
│   │       └── white.json
│   ├── robots.txt
│   └── site.webmanifest
├── spec/
│   ├── channels/
│   │   ├── imports_channel_spec.rb
│   │   ├── notifications_channel_spec.rb
│   │   ├── points_channel_spec.rb
│   │   └── tracks_channel_spec.rb
│   ├── controllers/
│   │   ├── api_controller_spec.rb
│   │   ├── application_controller_spec.rb
│   │   └── concerns/
│   │       └── safe_timestamp_parser_spec.rb
│   ├── factories/
│   │   ├── areas.rb
│   │   ├── countries.rb
│   │   ├── exports.rb
│   │   ├── families.rb
│   │   ├── family_invitations.rb
│   │   ├── family_location_requests.rb
│   │   ├── family_memberships.rb
│   │   ├── imports.rb
│   │   ├── notifications.rb
│   │   ├── place_visits.rb
│   │   ├── places.rb
│   │   ├── points.rb
│   │   ├── points_raw_data_archives.rb
│   │   ├── stats.rb
│   │   ├── taggings.rb
│   │   ├── tags.rb
│   │   ├── track_segments.rb
│   │   ├── tracks.rb
│   │   ├── trips.rb
│   │   ├── users/
│   │   │   └── digests.rb
│   │   ├── users.rb
│   │   └── visits.rb
│   ├── fixtures/
│   │   ├── files/
│   │   │   ├── geojson/
│   │   │   │   ├── export.json
│   │   │   │   ├── export_same_points.json
│   │   │   │   ├── google_takeout_example.json
│   │   │   │   └── gpslogger_example.json
│   │   │   ├── google/
│   │   │   │   ├── location-history/
│   │   │   │   │   ├── with_activitySegment_with_startLocation.json
│   │   │   │   │   ├── with_activitySegment_with_startLocation_timestampMs.json
│   │   │   │   │   ├── with_activitySegment_with_startLocation_timestamp_in_milliseconds_format.json
│   │   │   │   │   ├── with_activitySegment_with_startLocation_timestamp_in_seconds_format.json
│   │   │   │   │   ├── with_activitySegment_with_startLocation_with_iso_timestamp.json
│   │   │   │   │   ├── with_activitySegment_without_startLocation.json
│   │   │   │   │   ├── with_activitySegment_without_startLocation_without_waypointPath.json
│   │   │   │   │   ├── with_placeVisit_with_location_with_coordinates.json
│   │   │   │   │   ├── with_placeVisit_with_location_with_coordinates_with_iso_timestamp.json
│   │   │   │   │   ├── with_placeVisit_with_location_with_coordinates_with_milliseconds_timestamp.json
│   │   │   │   │   ├── with_placeVisit_with_location_with_coordinates_with_seconds_timestamp.json
│   │   │   │   │   ├── with_placeVisit_with_location_with_coordinates_with_timestampMs.json
│   │   │   │   │   ├── with_placeVisit_without_location_with_coordinates.json
│   │   │   │   │   └── with_placeVisit_without_location_with_coordinates_with_otherCandidateLocations.json
│   │   │   │   ├── location-history.json
│   │   │   │   ├── phone-takeout_w_3_duplicates.json
│   │   │   │   ├── records.json
│   │   │   │   └── semantic_history.json
│   │   │   ├── gpx/
│   │   │   │   ├── arc_example.gpx
│   │   │   │   ├── garmin_example.gpx
│   │   │   │   ├── gpx_track_multiple_segments.gpx
│   │   │   │   ├── gpx_track_multiple_tracks.gpx
│   │   │   │   └── gpx_track_single_segment.gpx
│   │   │   ├── immich/
│   │   │   │   ├── geodata.json
│   │   │   │   └── response.json
│   │   │   ├── kml/
│   │   │   │   ├── extended_data.kml
│   │   │   │   ├── gx_track.kml
│   │   │   │   ├── invalid_coordinates.kml
│   │   │   │   ├── large_track.kml
│   │   │   │   ├── linestring_track.kml
│   │   │   │   ├── multigeometry.kml
│   │   │   │   ├── nested_folders.kml
│   │   │   │   ├── points_with_timestamps.kml
│   │   │   │   ├── points_with_timestamps.kmz
│   │   │   │   └── timespan.kml
│   │   │   ├── overland/
│   │   │   │   └── geodata.json
│   │   │   ├── owntracks/
│   │   │   │   ├── 2023-02_old.rec
│   │   │   │   └── 2024-03.rec
│   │   │   ├── points/
│   │   │   │   └── geojson_example.json
│   │   │   └── watched/
│   │   │       ├── invalid_user@domain.com/
│   │   │       │   └── location-history.json
│   │   │       └── user@domain.com/
│   │   │           ├── 2023_January.json
│   │   │           ├── Records.json
│   │   │           ├── export_same_points.json
│   │   │           ├── gpx_track_single_segment.gpx
│   │   │           ├── location-history.json
│   │   │           └── owntracks.rec
│   │   └── users/
│   │       └── welcome
│   ├── helpers/
│   │   ├── application_helper_spec.rb
│   │   ├── insights_helper_spec.rb
│   │   └── stats_helper_spec.rb
│   ├── integration/
│   │   └── family_privacy_spec.rb
│   ├── jobs/
│   │   ├── app_version_checking_job_spec.rb
│   │   ├── application_job_spec.rb
│   │   ├── area_visits_calculating_job_spec.rb
│   │   ├── area_visits_calculation_scheduling_job_spec.rb
│   │   ├── bulk_stats_calculating_job_spec.rb
│   │   ├── bulk_visits_suggesting_job_spec.rb
│   │   ├── cache/
│   │   │   └── preheating_job_spec.rb
│   │   ├── concerns/
│   │   │   └── user_timezone_spec.rb
│   │   ├── data_migrations/
│   │   │   ├── backfill_motion_data_job_spec.rb
│   │   │   ├── backfill_onboarding_completed_job_spec.rb
│   │   │   ├── fix_route_opacity_job_spec.rb
│   │   │   ├── migrate_places_lonlat_job_spec.rb
│   │   │   ├── migrate_points_latlon_job_spec.rb
│   │   │   ├── set_points_country_ids_job_spec.rb
│   │   │   └── start_settings_points_country_ids_job_spec.rb
│   │   ├── enqueue_background_job_spec.rb
│   │   ├── export_job_spec.rb
│   │   ├── families/
│   │   │   └── expire_location_requests_job_spec.rb
│   │   ├── family/
│   │   │   └── invitations/
│   │   │       └── sending_job_spec.rb
│   │   ├── import/
│   │   │   ├── immich_geodata_job_spec.rb
│   │   │   ├── process_job_spec.rb
│   │   │   └── watcher_job_spec.rb
│   │   ├── imports/
│   │   │   └── destroy_job_spec.rb
│   │   ├── lite/
│   │   │   └── archival_warning_job_spec.rb
│   │   ├── places/
│   │   │   ├── bulk_name_fetching_job_spec.rb
│   │   │   └── name_fetching_job_spec.rb
│   │   ├── points/
│   │   │   ├── create_job_spec.rb
│   │   │   ├── nightly_reverse_geocoding_job_spec.rb
│   │   │   └── raw_data/
│   │   │       ├── archive_job_spec.rb
│   │   │       └── re_archive_month_job_spec.rb
│   │   ├── reverse_geocoding_job_spec.rb
│   │   ├── stale_jobs_recovery_job_spec.rb
│   │   ├── stats/
│   │   │   └── calculating_job_spec.rb
│   │   ├── tracks/
│   │   │   ├── daily_generation_job_spec.rb
│   │   │   ├── deduplication_job_spec.rb
│   │   │   ├── parallel_generator_job_spec.rb
│   │   │   ├── realtime_generation_job_spec.rb
│   │   │   ├── recalculate_job_spec.rb
│   │   │   └── transportation_mode_recalculation_job_spec.rb
│   │   ├── trips/
│   │   │   └── calculate_countries_job_spec.rb
│   │   ├── users/
│   │   │   ├── destroy_job_spec.rb
│   │   │   ├── digests/
│   │   │   │   ├── calculating_job_spec.rb
│   │   │   │   ├── email_sending_job_spec.rb
│   │   │   │   └── year_end_scheduling_job_spec.rb
│   │   │   ├── export_data_job_spec.rb
│   │   │   ├── import_data_job_spec.rb
│   │   │   ├── mailer_sending_job_spec.rb
│   │   │   ├── recalculate_data_job_spec.rb
│   │   │   └── trial_webhook_job_spec.rb
│   │   └── visit_suggesting_job_spec.rb
│   ├── lib/
│   │   ├── dawarich_settings_spec.rb
│   │   └── json_stream_handler_spec.rb
│   ├── mailers/
│   │   ├── family_mailer_spec.rb
│   │   ├── previews/
│   │   │   ├── users/
│   │   │   │   └── digests_mailer_preview.rb
│   │   │   └── users_mailer_preview.rb
│   │   └── users_mailer_spec.rb
│   ├── models/
│   │   ├── area_spec.rb
│   │   ├── concerns/
│   │   │   ├── archivable_spec.rb
│   │   │   ├── plan_scopable_spec.rb
│   │   │   ├── point_validation_spec.rb
│   │   │   ├── soft_deletable_spec.rb
│   │   │   ├── taggable_spec.rb
│   │   │   └── user_family_spec.rb
│   │   ├── country_spec.rb
│   │   ├── export_spec.rb
│   │   ├── family/
│   │   │   ├── invitation_spec.rb
│   │   │   ├── location_request_spec.rb
│   │   │   └── membership_spec.rb
│   │   ├── family_spec.rb
│   │   ├── import_spec.rb
│   │   ├── notification_spec.rb
│   │   ├── place_spec.rb
│   │   ├── place_visit_spec.rb
│   │   ├── point_spec.rb
│   │   ├── points/
│   │   │   └── raw_data_archive_spec.rb
│   │   ├── stat_spec.rb
│   │   ├── tag_spec.rb
│   │   ├── tagging_spec.rb
│   │   ├── track_segment_spec.rb
│   │   ├── track_spec.rb
│   │   ├── trip_spec.rb
│   │   ├── user_family_spec.rb
│   │   ├── user_spec.rb
│   │   ├── users/
│   │   │   └── digest_spec.rb
│   │   └── visit_spec.rb
│   ├── policies/
│   │   ├── family/
│   │   │   ├── invitation_policy_spec.rb
│   │   │   └── membership_policy_spec.rb
│   │   ├── import_policy_spec.rb
│   │   └── tag_policy_spec.rb
│   ├── queries/
│   │   ├── stats/
│   │   │   ├── daily_distance_query_spec.rb
│   │   │   └── time_of_day_query_spec.rb
│   │   └── stats_query_spec.rb
│   ├── rails_helper.rb
│   ├── requests/
│   │   ├── api/
│   │   │   └── v1/
│   │   │       ├── areas_spec.rb
│   │   │       ├── countries/
│   │   │       │   ├── borders_spec.rb
│   │   │       │   └── visited_cities_spec.rb
│   │   │       ├── digests_spec.rb
│   │   │       ├── families/
│   │   │       │   └── locations_spec.rb
│   │   │       ├── health_spec.rb
│   │   │       ├── imports_spec.rb
│   │   │       ├── insights_spec.rb
│   │   │       ├── locations_spec.rb
│   │   │       ├── maps/
│   │   │       │   └── hexagons_spec.rb
│   │   │       ├── overland/
│   │   │       │   └── batches_spec.rb
│   │   │       ├── owntracks/
│   │   │       │   └── points_spec.rb
│   │   │       ├── photos_spec.rb
│   │   │       ├── places_spec.rb
│   │   │       ├── plan_spec.rb
│   │   │       ├── points/
│   │   │       │   └── tracked_months_spec.rb
│   │   │       ├── points_spec.rb
│   │   │       ├── rate_limiting_spec.rb
│   │   │       ├── settings_spec.rb
│   │   │       ├── stats_spec.rb
│   │   │       ├── subscriptions_spec.rb
│   │   │       ├── tags_spec.rb
│   │   │       ├── timeline_spec.rb
│   │   │       ├── tracks/
│   │   │       │   └── points_spec.rb
│   │   │       ├── tracks_spec.rb
│   │   │       ├── users_spec.rb
│   │   │       ├── visits/
│   │   │       │   └── possible_places_spec.rb
│   │   │       └── visits_spec.rb
│   │   ├── areas_spec.rb
│   │   ├── authentication_spec.rb
│   │   ├── exports_spec.rb
│   │   ├── families_spec.rb
│   │   ├── family/
│   │   │   ├── invitations_spec.rb
│   │   │   ├── location_requests_spec.rb
│   │   │   ├── location_sharing_spec.rb
│   │   │   └── memberships_spec.rb
│   │   ├── family_workflows_spec.rb
│   │   ├── home_spec.rb
│   │   ├── imports_spec.rb
│   │   ├── insights_spec.rb
│   │   ├── map/
│   │   │   └── timeline_feeds_spec.rb
│   │   ├── map_spec.rb
│   │   ├── notifications_spec.rb
│   │   ├── places_spec.rb
│   │   ├── points_spec.rb
│   │   ├── settings/
│   │   │   ├── background_jobs_spec.rb
│   │   │   ├── general_spec.rb
│   │   │   ├── integrations_spec.rb
│   │   │   ├── maps_spec.rb
│   │   │   ├── onboarding_spec.rb
│   │   │   └── users_spec.rb
│   │   ├── settings_spec.rb
│   │   ├── shared/
│   │   │   ├── digests_spec.rb
│   │   │   └── stats_spec.rb
│   │   ├── sidekiq_spec.rb
│   │   ├── stats_spec.rb
│   │   ├── tags_spec.rb
│   │   ├── timezone_spec.rb
│   │   ├── trips_spec.rb
│   │   ├── users/
│   │   │   ├── digests_spec.rb
│   │   │   ├── omniauth_callbacks_spec.rb
│   │   │   ├── registrations_spec.rb
│   │   │   └── sessions_spec.rb
│   │   ├── users_spec.rb
│   │   └── visits_spec.rb
│   ├── serializers/
│   │   ├── api/
│   │   │   ├── digest_list_serializer_spec.rb
│   │   │   ├── photo_serializer_spec.rb
│   │   │   ├── place_serializer_spec.rb
│   │   │   ├── point_serializer_spec.rb
│   │   │   ├── slim_point_serializer_spec.rb
│   │   │   ├── user_serializer_spec.rb
│   │   │   └── visit_serializer_spec.rb
│   │   ├── export_serializer_spec.rb
│   │   ├── exports/
│   │   │   ├── point_geojson_serializer_spec.rb
│   │   │   └── point_gpx_serializer_spec.rb
│   │   ├── point_serializer_spec.rb
│   │   ├── points/
│   │   │   ├── geojson_serializer_spec.rb
│   │   │   └── gpx_serializer_spec.rb
│   │   ├── stats_serializer_spec.rb
│   │   ├── tag_serializer_spec.rb
│   │   ├── track_serializer_spec.rb
│   │   ├── tracks/
│   │   │   └── geojson_serializer_spec.rb
│   │   └── tracks_serializer_spec.rb
│   ├── services/
│   │   ├── areas/
│   │   │   └── visits/
│   │   │       └── create_spec.rb
│   │   ├── cache/
│   │   │   ├── clean_spec.rb
│   │   │   └── invalidate_user_caches_spec.rb
│   │   ├── check_app_version_spec.rb
│   │   ├── concerns/
│   │   │   └── ssl_configurable_spec.rb
│   │   ├── countries/
│   │   │   └── iso_code_mapper_spec.rb
│   │   ├── countries_and_cities_spec.rb
│   │   ├── exports/
│   │   │   └── create_spec.rb
│   │   ├── families/
│   │   │   ├── accept_invitation_spec.rb
│   │   │   ├── create_location_request_spec.rb
│   │   │   ├── create_spec.rb
│   │   │   ├── invite_spec.rb
│   │   │   ├── locations_spec.rb
│   │   │   ├── memberships/
│   │   │   │   └── destroy_spec.rb
│   │   │   └── update_location_sharing_spec.rb
│   │   ├── geojson/
│   │   │   ├── importer_spec.rb
│   │   │   └── params_spec.rb
│   │   ├── google_maps/
│   │   │   ├── phone_takeout_importer_spec.rb
│   │   │   ├── records_importer_spec.rb
│   │   │   ├── records_storage_importer_spec.rb
│   │   │   └── semantic_history_importer_spec.rb
│   │   ├── gpx/
│   │   │   └── track_importer_spec.rb
│   │   ├── immich/
│   │   │   ├── connection_tester_spec.rb
│   │   │   ├── import_geodata_spec.rb
│   │   │   ├── request_photos_spec.rb
│   │   │   ├── response_analyzer_spec.rb
│   │   │   └── response_validator_spec.rb
│   │   ├── imports/
│   │   │   ├── create_spec.rb
│   │   │   ├── destroy_spec.rb
│   │   │   ├── secure_file_downloader_spec.rb
│   │   │   ├── source_detector_spec.rb
│   │   │   └── watcher_spec.rb
│   │   ├── insights/
│   │   │   ├── activity_heatmap_calculator_spec.rb
│   │   │   ├── travel_insight_generator_spec.rb
│   │   │   ├── travel_patterns_loader_spec.rb
│   │   │   ├── year_comparison_calculator_spec.rb
│   │   │   └── year_totals_calculator_spec.rb
│   │   ├── jobs/
│   │   │   └── create_spec.rb
│   │   ├── kml/
│   │   │   └── importer_spec.rb
│   │   ├── location_search/
│   │   │   ├── geocoding_service_spec.rb
│   │   │   ├── point_finder_spec.rb
│   │   │   ├── result_aggregator_spec.rb
│   │   │   └── spatial_matcher_spec.rb
│   │   ├── maps/
│   │   │   ├── bounds_calculator_spec.rb
│   │   │   ├── hexagon_center_manager_spec.rb
│   │   │   ├── hexagon_polygon_generator_spec.rb
│   │   │   └── hexagon_request_handler_spec.rb
│   │   ├── metrics/
│   │   │   └── archives/
│   │   │       ├── compression_ratio_spec.rb
│   │   │       ├── count_mismatch_spec.rb
│   │   │       ├── operation_spec.rb
│   │   │       ├── points_archived_spec.rb
│   │   │       ├── size_spec.rb
│   │   │       └── verification_spec.rb
│   │   ├── notifications/
│   │   │   └── create_spec.rb
│   │   ├── overland/
│   │   │   ├── params_spec.rb
│   │   │   └── points_creator_spec.rb
│   │   ├── own_tracks/
│   │   │   ├── importer_spec.rb
│   │   │   ├── params_spec.rb
│   │   │   └── point_creator_spec.rb
│   │   ├── photoprism/
│   │   │   ├── cache_preview_token_spec.rb
│   │   │   ├── connection_tester_spec.rb
│   │   │   ├── import_geodata_spec.rb
│   │   │   ├── request_photos_spec.rb
│   │   │   └── response_validator_spec.rb
│   │   ├── photos/
│   │   │   ├── cache_cleaner_spec.rb
│   │   │   ├── importer_spec.rb
│   │   │   ├── search_spec.rb
│   │   │   └── thumbnail_spec.rb
│   │   ├── places/
│   │   │   └── name_fetcher_spec.rb
│   │   ├── points/
│   │   │   ├── create_spec.rb
│   │   │   ├── live_broadcaster_spec.rb
│   │   │   ├── motion_data_extractor_spec.rb
│   │   │   ├── params_spec.rb
│   │   │   ├── raw_data/
│   │   │   │   ├── archiver_spec.rb
│   │   │   │   ├── chunk_compressor_spec.rb
│   │   │   │   ├── clearer_spec.rb
│   │   │   │   ├── encryption_spec.rb
│   │   │   │   ├── restorer_spec.rb
│   │   │   │   └── verifier_spec.rb
│   │   │   └── raw_data_lonlat_extractor_spec.rb
│   │   ├── points_limit_exceeded_spec.rb
│   │   ├── reverse_geocoding/
│   │   │   ├── places/
│   │   │   │   └── fetch_data_spec.rb
│   │   │   └── points/
│   │   │       └── fetch_data_spec.rb
│   │   ├── settings/
│   │   │   └── update_spec.rb
│   │   ├── stats/
│   │   │   ├── bulk_calculator_spec.rb
│   │   │   ├── calculate_month_spec.rb
│   │   │   └── hexagon_calculator_spec.rb
│   │   ├── subscription/
│   │   │   └── encode_jwt_token_spec.rb
│   │   ├── tasks/
│   │   │   └── imports/
│   │   │       └── google_records_spec.rb
│   │   ├── timeline/
│   │   │   └── day_assembler_spec.rb
│   │   ├── tracks/
│   │   │   ├── boundary_detector_spec.rb
│   │   │   ├── build_path_spec.rb
│   │   │   ├── deduplicator_spec.rb
│   │   │   ├── incremental_generator_spec.rb
│   │   │   ├── index_query_spec.rb
│   │   │   ├── merger_spec.rb
│   │   │   ├── parallel_generator_spec.rb
│   │   │   ├── realtime_debouncer_spec.rb
│   │   │   ├── segmentation_spec.rb
│   │   │   ├── session_manager_spec.rb
│   │   │   ├── time_chunker_spec.rb
│   │   │   ├── track_builder_spec.rb
│   │   │   └── transportation_recalculation_status_spec.rb
│   │   ├── transportation_modes/
│   │   │   ├── detector_spec.rb
│   │   │   ├── mode_classifier_spec.rb
│   │   │   ├── movement_analyzer_spec.rb
│   │   │   └── source_data_extractor_spec.rb
│   │   ├── trips/
│   │   │   └── photos_spec.rb
│   │   ├── users/
│   │   │   ├── destroy_spec.rb
│   │   │   ├── digests/
│   │   │   │   ├── activity_breakdown_calculator_spec.rb
│   │   │   │   ├── calculate_year_spec.rb
│   │   │   │   ├── first_time_visits_calculator_spec.rb
│   │   │   │   └── year_over_year_calculator_spec.rb
│   │   │   ├── export_data/
│   │   │   │   ├── areas_spec.rb
│   │   │   │   ├── digests_spec.rb
│   │   │   │   ├── exports_spec.rb
│   │   │   │   ├── imports_spec.rb
│   │   │   │   ├── notifications_spec.rb
│   │   │   │   ├── places_spec.rb
│   │   │   │   ├── points_spec.rb
│   │   │   │   ├── stats_spec.rb
│   │   │   │   ├── tracks_spec.rb
│   │   │   │   ├── trips_spec.rb
│   │   │   │   └── visits_spec.rb
│   │   │   ├── export_data_spec.rb
│   │   │   ├── export_import_integration_spec.rb
│   │   │   ├── import_data/
│   │   │   │   ├── areas_spec.rb
│   │   │   │   ├── digests_spec.rb
│   │   │   │   ├── exports_spec.rb
│   │   │   │   ├── imports_spec.rb
│   │   │   │   ├── notifications_spec.rb
│   │   │   │   ├── places_spec.rb
│   │   │   │   ├── places_streaming_spec.rb
│   │   │   │   ├── points_spec.rb
│   │   │   │   ├── raw_data_archives_spec.rb
│   │   │   │   ├── settings_spec.rb
│   │   │   │   ├── stats_spec.rb
│   │   │   │   ├── taggings_spec.rb
│   │   │   │   ├── tags_spec.rb
│   │   │   │   ├── tracks_spec.rb
│   │   │   │   ├── trips_spec.rb
│   │   │   │   ├── v1_handler_spec.rb
│   │   │   │   ├── v2_handler_spec.rb
│   │   │   │   └── visits_spec.rb
│   │   │   ├── import_data_spec.rb
│   │   │   ├── safe_settings_spec.rb
│   │   │   └── transportation_thresholds_updater_spec.rb
│   │   └── visits/
│   │       ├── bulk_update_spec.rb
│   │       ├── create_spec.rb
│   │       ├── creator_spec.rb
│   │       ├── detector_spec.rb
│   │       ├── find_in_time_spec.rb
│   │       ├── find_within_bounding_box_spec.rb
│   │       ├── finder_spec.rb
│   │       ├── group_spec.rb
│   │       ├── merge_service_spec.rb
│   │       ├── merger_spec.rb
│   │       ├── names/
│   │       │   ├── builder_spec.rb
│   │       │   └── suggester_spec.rb
│   │       ├── place_finder_spec.rb
│   │       ├── smart_detect_spec.rb
│   │       ├── suggest_spec.rb
│   │       └── time_chunks_spec.rb
│   ├── spec_helper.rb
│   ├── support/
│   │   ├── capybara.rb
│   │   ├── devise.rb
│   │   ├── geocoder_stubs.rb
│   │   ├── github_api_stubs.rb
│   │   ├── omniauth.rb
│   │   ├── pundit_matchers.rb
│   │   ├── redis.rb
│   │   ├── swagger_response_example.rb
│   │   └── turbo_stream_helpers.rb
│   ├── swagger/
│   │   └── api/
│   │       └── v1/
│   │           ├── areas_controller_spec.rb
│   │           ├── countries/
│   │           │   ├── borders_controller_spec.rb
│   │           │   └── visited_cities_spec.rb
│   │           ├── digests_controller_spec.rb
│   │           ├── families/
│   │           │   └── locations_controller_spec.rb
│   │           ├── health_controller_spec.rb
│   │           ├── imports_controller_spec.rb
│   │           ├── insights_controller_spec.rb
│   │           ├── locations_controller_spec.rb
│   │           ├── maps/
│   │           │   └── hexagons_controller_spec.rb
│   │           ├── overland/
│   │           │   └── batches_controller_spec.rb
│   │           ├── owntracks/
│   │           │   └── points_controller_spec.rb
│   │           ├── photos_controller_spec.rb
│   │           ├── places_controller_spec.rb
│   │           ├── points/
│   │           │   └── tracked_months_controller_spec.rb
│   │           ├── points_controller_spec.rb
│   │           ├── settings_controller_spec.rb
│   │           ├── stats_controller_spec.rb
│   │           ├── subscriptions_controller_spec.rb
│   │           ├── tags_controller_spec.rb
│   │           ├── timeline_controller_spec.rb
│   │           ├── tracks/
│   │           │   └── points_controller_spec.rb
│   │           ├── tracks_controller_spec.rb
│   │           ├── users_controller_spec.rb
│   │           └── visits_controller_spec.rb
│   ├── swagger_helper.rb
│   └── tasks/
│       ├── import_spec.rb
│       └── points_raw_data_reset_all_spec.rb
├── storage/
│   └── .keep
├── swagger/
│   └── v1/
│       └── swagger.yaml
├── tmp/
│   └── .keep
└── vendor/
    ├── .keep
    └── javascript/
        ├── .keep
        ├── @rails--ujs.js
        ├── emoji-mart.js
        ├── leaflet-draw.js
        ├── leaflet-providers.js
        ├── leaflet.control.layers.tree.js
        ├── leaflet.heat.js
        ├── leaflet.js
        └── maplibre-gl.js
Download .txt
Showing preview only (821K chars total). Download the full file or copy to clipboard to get everything.
SYMBOL INDEX (8919 symbols across 721 files)

FILE: app/channels/application_cable/channel.rb
  type ApplicationCable (line 3) | module ApplicationCable
    class Channel (line 4) | class Channel < ActionCable::Channel::Base

FILE: app/channels/application_cable/connection.rb
  type ApplicationCable (line 3) | module ApplicationCable
    class Connection (line 4) | class Connection < ActionCable::Connection::Base
      method connect (line 7) | def connect
      method find_verified_user (line 13) | def find_verified_user

FILE: app/channels/family_locations_channel.rb
  class FamilyLocationsChannel (line 3) | class FamilyLocationsChannel < ApplicationCable::Channel
    method subscribed (line 4) | def subscribed
    method unsubscribed (line 11) | def unsubscribed

FILE: app/channels/imports_channel.rb
  class ImportsChannel (line 3) | class ImportsChannel < ApplicationCable::Channel
    method subscribed (line 4) | def subscribed

FILE: app/channels/notifications_channel.rb
  class NotificationsChannel (line 3) | class NotificationsChannel < ApplicationCable::Channel
    method subscribed (line 4) | def subscribed

FILE: app/channels/points_channel.rb
  class PointsChannel (line 3) | class PointsChannel < ApplicationCable::Channel
    method subscribed (line 4) | def subscribed

FILE: app/channels/tracks_channel.rb
  class TracksChannel (line 3) | class TracksChannel < ApplicationCable::Channel
    method subscribed (line 4) | def subscribed

FILE: app/controllers/api/v1/areas_controller.rb
  class Api::V1::AreasController (line 3) | class Api::V1::AreasController < ApiController
    method index (line 6) | def index
    method show (line 12) | def show
    method create (line 16) | def create
    method update (line 26) | def update
    method destroy (line 34) | def destroy
    method set_area (line 42) | def set_area
    method area_params (line 46) | def area_params

FILE: app/controllers/api/v1/countries/borders_controller.rb
  class Api::V1::Countries::BordersController (line 3) | class Api::V1::Countries::BordersController < ApiController
    method index (line 4) | def index

FILE: app/controllers/api/v1/countries/visited_cities_controller.rb
  class Api::V1::Countries::VisitedCitiesController (line 3) | class Api::V1::Countries::VisitedCitiesController < ApiController
    method index (line 8) | def index
    method required_params (line 28) | def required_params

FILE: app/controllers/api/v1/digests_controller.rb
  class Api::V1::DigestsController (line 3) | class Api::V1::DigestsController < ApiController
    method index (line 6) | def index
    method show (line 13) | def show
    method create (line 22) | def create
    method destroy (line 39) | def destroy
    method authenticate_active_api_user! (line 47) | def authenticate_active_api_user!
    method available_years_for_generation (line 53) | def available_years_for_generation
    method valid_year? (line 60) | def valid_year?(year)
    method distance_unit (line 66) | def distance_unit

FILE: app/controllers/api/v1/families/locations_controller.rb
  class Api::V1::Families::LocationsController (line 3) | class Api::V1::Families::LocationsController < ApiController
    method index (line 7) | def index
    method history (line 17) | def history
    method ensure_user_in_family! (line 42) | def ensure_user_in_family!

FILE: app/controllers/api/v1/health_controller.rb
  class Api::V1::HealthController (line 3) | class Api::V1::HealthController < ApiController
    method index (line 6) | def index

FILE: app/controllers/api/v1/imports_controller.rb
  class Api::V1::ImportsController (line 3) | class Api::V1::ImportsController < ApiController
    method index (line 11) | def index
    method show (line 25) | def show
    method create (line 31) | def create
    method generate_unique_import_name (line 61) | def generate_unique_import_name(original_name)
    method validate_file_type (line 70) | def validate_file_type
    method serialize_import (line 81) | def serialize_import(import)

FILE: app/controllers/api/v1/insights_controller.rb
  class Api::V1::InsightsController (line 3) | class Api::V1::InsightsController < ApiController
    method index (line 4) | def index
    method details (line 21) | def details
    method load_year_data (line 39) | def load_year_data
    method load_totals (line 45) | def load_totals
    method load_heatmap (line 49) | def load_heatmap
    method load_comparison (line 53) | def load_comparison
    method load_travel_patterns (line 62) | def load_travel_patterns
    method calculate_yearly_day_of_week (line 75) | def calculate_yearly_day_of_week
    method fetch_yearly_top_visits (line 90) | def fetch_yearly_top_visits
    method distance_unit (line 100) | def distance_unit
    method plan_metadata (line 104) | def plan_metadata

FILE: app/controllers/api/v1/locations_controller.rb
  class Api::V1::LocationsController (line 3) | class Api::V1::LocationsController < ApiController
    method index (line 7) | def index
    method suggestions (line 21) | def suggestions
    method search_query (line 46) | def search_query
    method search_params (line 50) | def search_params
    method coordinate_search? (line 61) | def coordinate_search?
    method validate_search_params (line 65) | def validate_search_params
    method validate_suggestion_params (line 83) | def validate_suggestion_params
    method parse_date (line 92) | def parse_date(date_string)

FILE: app/controllers/api/v1/maps/hexagons_controller.rb
  class Api::V1::Maps::HexagonsController (line 3) | class Api::V1::Maps::HexagonsController < ApiController
    method index (line 6) | def index
    method bounds (line 30) | def bounds
    method resolve_hexagon_context (line 59) | def resolve_hexagon_context
    method resolve_public_sharing_context (line 65) | def resolve_public_sharing_context
    method resolve_authenticated_context (line 77) | def resolve_authenticated_context
    method handle_service_error (line 86) | def handle_service_error
    method public_sharing_request? (line 90) | def public_sharing_request?

FILE: app/controllers/api/v1/overland/batches_controller.rb
  class Api::V1::Overland::BatchesController (line 3) | class Api::V1::Overland::BatchesController < ApiController
    method create (line 7) | def create
    method batch_params (line 19) | def batch_params

FILE: app/controllers/api/v1/owntracks/points_controller.rb
  class Api::V1::Owntracks::PointsController (line 3) | class Api::V1::Owntracks::PointsController < ApiController
    method create (line 7) | def create
    method point_params (line 19) | def point_params

FILE: app/controllers/api/v1/photos_controller.rb
  class Api::V1::PhotosController (line 3) | class Api::V1::PhotosController < ApiController
    method index (line 7) | def index
    method thumbnail (line 22) | def thumbnail
    method fetch_cached_thumbnail (line 29) | def fetch_cached_thumbnail(source)
    method handle_thumbnail_response (line 39) | def handle_thumbnail_response(response)
    method thumbnail_error (line 48) | def thumbnail_error(response)
    method integration_configured? (line 54) | def integration_configured?
    method check_integration_configured (line 58) | def check_integration_configured
    method check_source (line 62) | def check_source
    method unauthorized_integration (line 66) | def unauthorized_integration

FILE: app/controllers/api/v1/places_controller.rb
  type Api (line 3) | module Api
    type V1 (line 4) | module V1
      class PlacesController (line 5) | class PlacesController < ApiController
        method index (line 8) | def index
        method show (line 57) | def show
        method create (line 61) | def create
        method update (line 74) | def update
        method destroy (line 85) | def destroy
        method nearby (line 91) | def nearby
        method set_place (line 108) | def set_place
        method place_params (line 112) | def place_params
        method tag_ids (line 116) | def tag_ids
        method add_tags (line 121) | def add_tags
        method set_tags (line 128) | def set_tags
        method serialize_place (line 134) | def serialize_place(place)

FILE: app/controllers/api/v1/plan_controller.rb
  class Api::V1::PlanController (line 3) | class Api::V1::PlanController < ApiController
    method show (line 4) | def show
    method full_features (line 16) | def full_features
    method lite_features (line 22) | def lite_features

FILE: app/controllers/api/v1/points/tracked_months_controller.rb
  class Api::V1::Points::TrackedMonthsController (line 3) | class Api::V1::Points::TrackedMonthsController < ApiController
    method index (line 4) | def index

FILE: app/controllers/api/v1/points_controller.rb
  class Api::V1::PointsController (line 3) | class Api::V1::PointsController < ApiController
    method index (line 10) | def index
    method create (line 55) | def create
    method update (line 61) | def update
    method destroy (line 78) | def destroy
    method bulk_destroy (line 85) | def bulk_destroy
    method point_params (line 97) | def point_params
    method batch_params (line 101) | def batch_params
    method bulk_destroy_params (line 105) | def bulk_destroy_params
    method point_serializer (line 109) | def point_serializer

FILE: app/controllers/api/v1/settings_controller.rb
  class Api::V1::SettingsController (line 3) | class Api::V1::SettingsController < ApiController
    method index (line 6) | def index
    method update (line 16) | def update
    method transportation_recalculation_status (line 33) | def transportation_recalculation_status
    method recalculation_status_manager (line 47) | def recalculation_status_manager
    method settings_params (line 51) | def settings_params

FILE: app/controllers/api/v1/stats_controller.rb
  class Api::V1::StatsController (line 3) | class Api::V1::StatsController < ApiController
    method index (line 4) | def index

FILE: app/controllers/api/v1/subscriptions_controller.rb
  class Api::V1::SubscriptionsController (line 3) | class Api::V1::SubscriptionsController < ApiController
    method callback (line 6) | def callback

FILE: app/controllers/api/v1/tags_controller.rb
  type Api (line 3) | module Api
    type V1 (line 4) | module V1
      class TagsController (line 5) | class TagsController < ApiController
        method privacy_zones (line 6) | def privacy_zones

FILE: app/controllers/api/v1/timeline_controller.rb
  type Api (line 3) | module Api
    type V1 (line 4) | module V1
      class TimelineController (line 5) | class TimelineController < ApiController
        method index (line 8) | def index
        method date_params_present? (line 32) | def date_params_present?
        method range_too_large? (line 36) | def range_too_large?

FILE: app/controllers/api/v1/tracks/points_controller.rb
  class Api::V1::Tracks::PointsController (line 3) | class Api::V1::Tracks::PointsController < ApiController
    method index (line 4) | def index

FILE: app/controllers/api/v1/tracks_controller.rb
  class Api::V1::TracksController (line 3) | class Api::V1::TracksController < ApiController
    method index (line 4) | def index
    method show (line 17) | def show

FILE: app/controllers/api/v1/users_controller.rb
  class Api::V1::UsersController (line 3) | class Api::V1::UsersController < ApiController
    method me (line 4) | def me

FILE: app/controllers/api/v1/visits/possible_places_controller.rb
  class Api::V1::Visits::PossiblePlacesController (line 3) | class Api::V1::Visits::PossiblePlacesController < ApiController
    method index (line 4) | def index

FILE: app/controllers/api/v1/visits_controller.rb
  class Api::V1::VisitsController (line 3) | class Api::V1::VisitsController < ApiController
    method index (line 4) | def index
    method show (line 24) | def show
    method create (line 29) | def create
    method update (line 42) | def update
    method merge (line 49) | def merge
    method bulk_update (line 75) | def bulk_update
    method destroy (line 94) | def destroy
    method visit_params (line 111) | def visit_params
    method merge_params (line 115) | def merge_params
    method bulk_update_params (line 119) | def bulk_update_params
    method update_visit (line 123) | def update_visit(visit)

FILE: app/controllers/api_controller.rb
  class ApiController (line 3) | class ApiController < ApplicationController
    method set_user_time_zone (line 13) | def set_user_time_zone(&block)
    method record_not_found (line 24) | def record_not_found
    method set_version_header (line 28) | def set_version_header
    method authenticate_api_key (line 35) | def authenticate_api_key
    method require_pro_api! (line 41) | def require_pro_api!
    method require_write_api! (line 53) | def require_write_api!
    method scoped_points (line 67) | def scoped_points(user = current_api_user)
    method apply_plan_scope (line 73) | def apply_plan_scope(relation, user = current_api_user)
    method upgrade_url_for (line 80) | def upgrade_url_for(user)
    method authenticate_active_api_user! (line 84) | def authenticate_active_api_user!
    method current_api_user (line 100) | def current_api_user
    method api_key (line 104) | def api_key
    method validate_params (line 108) | def validate_params
    method required_params (line 120) | def required_params
    method validate_points_limit (line 124) | def validate_points_limit
    method set_rate_limit_headers (line 130) | def set_rate_limit_headers

FILE: app/controllers/application_controller.rb
  class ApplicationController (line 3) | class ApplicationController < ActionController::Base
    method unread_notifications (line 14) | def unread_notifications
    method authenticate_admin! (line 20) | def authenticate_admin!
    method authenticate_self_hosted! (line 26) | def authenticate_self_hosted!
    method authenticate_active_user! (line 32) | def authenticate_active_user!
    method authenticate_non_self_hosted! (line 38) | def authenticate_non_self_hosted!
    method after_sign_in_path_for (line 44) | def after_sign_in_path_for(resource)
    method require_pro! (line 69) | def require_pro!
    method ensure_family_feature_enabled! (line 100) | def ensure_family_feature_enabled!
    method sign_out_deleted_users (line 108) | def sign_out_deleted_users
    method set_user_time_zone (line 115) | def set_user_time_zone(&block)
    method set_self_hosted_status (line 126) | def set_self_hosted_status
    method store_client_header (line 130) | def store_client_header
    method user_not_authorized (line 136) | def user_not_authorized

FILE: app/controllers/areas_controller.rb
  class AreasController (line 3) | class AreasController < ApplicationController
    method create (line 8) | def create
    method area_params (line 28) | def area_params

FILE: app/controllers/auth/ios_controller.rb
  type Auth (line 3) | module Auth
    class IosController (line 4) | class IosController < ApplicationController
      method success (line 5) | def success

FILE: app/controllers/concerns/flash_streamable.rb
  type FlashStreamable (line 3) | module FlashStreamable
    function stream_flash (line 8) | def stream_flash(type, message)

FILE: app/controllers/concerns/safe_timestamp_parser.rb
  type SafeTimestampParser (line 3) | module SafeTimestampParser
    function safe_timestamp (line 8) | def safe_timestamp(date_string)

FILE: app/controllers/concerns/sortable.rb
  type Sortable (line 3) | module Sortable
    function sorted (line 8) | def sorted(scope)
    function sort_column (line 17) | def sort_column
    function sort_direction (line 21) | def sort_direction

FILE: app/controllers/concerns/utm_trackable.rb
  type UtmTrackable (line 3) | module UtmTrackable
    function store_utm_params (line 8) | def store_utm_params
    function assign_utm_params (line 14) | def assign_utm_params(record)
    function extract_utm_data_from_session (line 25) | def extract_utm_data_from_session
    function clear_utm_session (line 31) | def clear_utm_session

FILE: app/controllers/exports_controller.rb
  class ExportsController (line 3) | class ExportsController < ApplicationController
    method index (line 12) | def index
    method create (line 17) | def create
    method destroy (line 37) | def destroy
    method set_export (line 45) | def set_export

FILE: app/controllers/families_controller.rb
  class FamiliesController (line 3) | class FamiliesController < ApplicationController
    method show (line 8) | def show
    method new (line 21) | def new
    method create (line 28) | def create
    method edit (line 55) | def edit
    method update (line 59) | def update
    method destroy (line 69) | def destroy
    method set_family (line 82) | def set_family
    method family_params (line 87) | def family_params

FILE: app/controllers/family/invitations_controller.rb
  class Family::InvitationsController (line 3) | class Family::InvitationsController < ApplicationController
    method index (line 9) | def index
    method show (line 15) | def show
    method create (line 26) | def create
    method destroy (line 42) | def destroy
    method set_family (line 59) | def set_family
    method set_invitation_by_id_and_family (line 65) | def set_invitation_by_id_and_family
    method invitation_params (line 72) | def invitation_params

FILE: app/controllers/family/location_requests_controller.rb
  class Family::LocationRequestsController (line 3) | class Family::LocationRequestsController < ApplicationController
    method create (line 10) | def create
    method show (line 27) | def show
    method accept (line 31) | def accept
    method decline (line 46) | def decline
    method set_request (line 59) | def set_request
    method authorize_target_user! (line 63) | def authorize_target_user!
    method ensure_user_in_family! (line 69) | def ensure_user_in_family!
    method actionable? (line 75) | def actionable?

FILE: app/controllers/family/location_sharing_controller.rb
  class Family::LocationSharingController (line 3) | class Family::LocationSharingController < ApplicationController
    method update (line 10) | def update
    method ensure_user_in_family! (line 43) | def ensure_user_in_family!

FILE: app/controllers/family/memberships_controller.rb
  class Family::MembershipsController (line 3) | class Family::MembershipsController < ApplicationController
    method create (line 10) | def create
    method destroy (line 41) | def destroy
    method set_family (line 60) | def set_family
    method set_membership (line 66) | def set_membership
    method set_invitation (line 70) | def set_invitation

FILE: app/controllers/home_controller.rb
  class HomeController (line 3) | class HomeController < ApplicationController
    method index (line 6) | def index

FILE: app/controllers/imports_controller.rb
  class ImportsController (line 3) | class ImportsController < ApplicationController
    method index (line 18) | def index
    method show (line 26) | def show; end
    method edit (line 28) | def edit; end
    method new (line 30) | def new
    method update (line 36) | def update
    method create (line 42) | def create
    method destroy (line 71) | def destroy
    method set_import (line 83) | def set_import
    method authorize_import (line 87) | def authorize_import
    method import_params (line 91) | def import_params
    method extract_raw_files (line 95) | def extract_raw_files
    method process_raw_files (line 100) | def process_raw_files(raw_files)
    method cleanup_failed_imports (line 109) | def cleanup_failed_imports
    method report_import_error (line 116) | def report_import_error(error)
    method create_import_from_signed_id (line 122) | def create_import_from_signed_id(signed_id)
    method generate_unique_import_name (line 136) | def generate_unique_import_name(original_name)
    method validate_points_limit (line 148) | def validate_points_limit

FILE: app/controllers/insights_controller.rb
  class InsightsController (line 3) | class InsightsController < ApplicationController
    method index (line 6) | def index
    method details (line 21) | def details
    method set_available_years (line 45) | def set_available_years
    method year_locked? (line 51) | def year_locked?
    method load_year_stats (line 57) | def load_year_stats
    method load_year_totals (line 71) | def load_year_totals
    method load_comparison_data (line 82) | def load_comparison_data
    method load_activity_heatmap (line 100) | def load_activity_heatmap
    method load_yearly_patterns (line 106) | def load_yearly_patterns
    method fetch_or_calculate_yearly_digest (line 117) | def fetch_or_calculate_yearly_digest
    method calculate_and_cache_digest (line 138) | def calculate_and_cache_digest
    method digest_stale? (line 142) | def digest_stale?(digest)
    method calculate_yearly_day_of_week (line 153) | def calculate_yearly_day_of_week
    method fetch_yearly_top_visits (line 170) | def fetch_yearly_top_visits
    method load_monthly_digest (line 180) | def load_monthly_digest
    method determine_selected_month (line 198) | def determine_selected_month
    method set_default_patterns (line 208) | def set_default_patterns
    method distance_unit (line 218) | def distance_unit

FILE: app/controllers/map/leaflet_controller.rb
  class Map::LeafletController (line 3) | class Map::LeafletController < ApplicationController
    method index (line 9) | def index
    method filtered_points (line 24) | def filtered_points
    method build_coordinates (line 28) | def build_coordinates
    method extract_track_ids (line 33) | def extract_track_ids
    method build_tracks (line 37) | def build_tracks
    method calculate_distance (line 43) | def calculate_distance
    method parsed_start_at (line 74) | def parsed_start_at
    method parsed_end_at (line 78) | def parsed_end_at
    method years_range (line 82) | def years_range
    method points_count (line 86) | def points_count
    method start_at (line 90) | def start_at
    method end_at (line 97) | def end_at
    method points (line 104) | def points
    method points_from_import (line 108) | def points_from_import
    method points_from_user (line 112) | def points_from_user

FILE: app/controllers/map/maplibre_controller.rb
  type Map (line 3) | module Map
    class MaplibreController (line 4) | class MaplibreController < ApplicationController
      method index (line 10) | def index
      method start_at (line 17) | def start_at
      method end_at (line 23) | def end_at
      method parsed_start_at (line 29) | def parsed_start_at
      method parsed_end_at (line 33) | def parsed_end_at

FILE: app/controllers/map/timeline_feeds_controller.rb
  type Map (line 3) | module Map
    class TimelineFeedsController (line 4) | class TimelineFeedsController < ApplicationController
      method index (line 10) | def index
      method track_info (line 20) | def track_info
      method parsed_start_at (line 27) | def parsed_start_at
      method parsed_end_at (line 31) | def parsed_end_at

FILE: app/controllers/metrics_controller.rb
  class MetricsController (line 3) | class MetricsController < ApplicationController
    method index (line 6) | def index

FILE: app/controllers/notifications_controller.rb
  class NotificationsController (line 3) | class NotificationsController < ApplicationController
    method index (line 7) | def index
    method show (line 12) | def show
    method mark_as_read (line 16) | def mark_as_read
    method destroy_all (line 21) | def destroy_all
    method destroy (line 26) | def destroy
    method set_notification (line 33) | def set_notification

FILE: app/controllers/places_controller.rb
  class PlacesController (line 3) | class PlacesController < ApplicationController
    method index (line 9) | def index
    method create (line 13) | def create
    method update (line 37) | def update
    method nearby (line 59) | def nearby
    method destroy (line 76) | def destroy
    method set_place (line 84) | def set_place
    method place_params (line 88) | def place_params
    method tag_ids (line 92) | def tag_ids
    method add_tags (line 97) | def add_tags
    method set_tags (line 102) | def set_tags
    method place_data_element (line 108) | def place_data_element(updated: false)
    method serialize_place (line 117) | def serialize_place(place)

FILE: app/controllers/points_controller.rb
  class PointsController (line 3) | class PointsController < ApplicationController
    method index (line 8) | def index
    method bulk_destroy (line 22) | def bulk_destroy
    method point_params (line 40) | def point_params
    method start_at (line 44) | def start_at
    method end_at (line 50) | def end_at
    method points (line 56) | def points
    method import_points (line 60) | def import_points
    method user_points (line 64) | def user_points
    method order_by (line 68) | def order_by
    method preserved_params (line 72) | def preserved_params

FILE: app/controllers/settings/background_jobs_controller.rb
  class Settings::BackgroundJobsController (line 3) | class Settings::BackgroundJobsController < ApplicationController
    method index (line 13) | def index; end
    method update (line 15) | def update
    method create (line 26) | def create
    method settings_params (line 44) | def settings_params

FILE: app/controllers/settings/general_controller.rb
  class Settings::GeneralController (line 3) | class Settings::GeneralController < ApplicationController
    method index (line 6) | def index; end
    method update (line 8) | def update
    method verify_supporter (line 20) | def verify_supporter
    method update_timezone (line 44) | def update_timezone
    method update_email_settings (line 50) | def update_email_settings
    method update_supporter_settings (line 59) | def update_supporter_settings

FILE: app/controllers/settings/integrations_controller.rb
  class Settings::IntegrationsController (line 3) | class Settings::IntegrationsController < ApplicationController
    method index (line 8) | def index
    method update (line 12) | def update
    method settings_params (line 27) | def settings_params

FILE: app/controllers/settings/maps_controller.rb
  class Settings::MapsController (line 3) | class Settings::MapsController < ApplicationController
    method index (line 6) | def index
    method update (line 10) | def update
    method settings_params (line 19) | def settings_params

FILE: app/controllers/settings/onboardings_controller.rb
  type Settings (line 3) | module Settings
    class OnboardingsController (line 4) | class OnboardingsController < ApplicationController
      method update (line 7) | def update

FILE: app/controllers/settings/users_controller.rb
  class Settings::UsersController (line 3) | class Settings::UsersController < ApplicationController
    method index (line 8) | def index
    method show (line 12) | def show
    method edit (line 16) | def edit
    method update (line 20) | def update
    method create (line 34) | def create
    method destroy (line 48) | def destroy
    method regenerate_api_key (line 64) | def regenerate_api_key
    method send_password_reset (line 71) | def send_password_reset
    method update_registration_settings (line 78) | def update_registration_settings
    method export (line 86) | def export
    method import (line 92) | def import
    method filtered_users (line 116) | def filtered_users
    method user_params (line 122) | def user_params
    method filtered_user_params (line 126) | def filtered_user_params
    method last_admin_protection_needed? (line 132) | def last_admin_protection_needed?
    method removing_admin_role? (line 138) | def removing_admin_role?
    method disabling_user? (line 142) | def disabling_user?
    method sole_admin? (line 146) | def sole_admin?
    method last_admin_alert_message (line 150) | def last_admin_alert_message
    method create_import_from_signed_archive_id (line 158) | def create_import_from_signed_archive_id(signed_id)
    method generate_unique_import_name (line 176) | def generate_unique_import_name(original_name)
    method validate_archive_file (line 188) | def validate_archive_file(archive_file)
    method validate_blob_file_type (line 196) | def validate_blob_file_type(blob)

FILE: app/controllers/settings_controller.rb
  class SettingsController (line 3) | class SettingsController < ApplicationController
    method theme (line 6) | def theme
    method generate_api_key (line 12) | def generate_api_key

FILE: app/controllers/shared/digests_controller.rb
  class Shared::DigestsController (line 3) | class Shared::DigestsController < ApplicationController
    method show (line 10) | def show
    method update (line 27) | def update

FILE: app/controllers/shared/stats_controller.rb
  class Shared::StatsController (line 3) | class Shared::StatsController < ApplicationController
    method show (line 10) | def show
    method update (line 28) | def update

FILE: app/controllers/stats_controller.rb
  class StatsController (line 3) | class StatsController < ApplicationController
    method index (line 7) | def index
    method show (line 14) | def show
    method month (line 20) | def month
    method update (line 29) | def update
    method update_all (line 45) | def update_all
    method assign_points_statistics (line 59) | def assign_points_statistics
    method precompute_year_distances (line 67) | def precompute_year_distances
    method locked_years (line 84) | def locked_years
    method build_stats (line 92) | def build_stats

FILE: app/controllers/tags_controller.rb
  class TagsController (line 3) | class TagsController < ApplicationController
    method index (line 7) | def index
    method new (line 13) | def new
    method create (line 19) | def create
    method edit (line 31) | def edit
    method update (line 35) | def update
    method destroy (line 45) | def destroy
    method set_tag (line 55) | def set_tag
    method tag_params (line 59) | def tag_params

FILE: app/controllers/trips_controller.rb
  class TripsController (line 3) | class TripsController < ApplicationController
    method index (line 9) | def index
    method show (line 13) | def show
    method new (line 22) | def new
    method edit (line 27) | def edit; end
    method create (line 29) | def create
    method update (line 39) | def update
    method destroy (line 47) | def destroy
    method set_trip (line 54) | def set_trip
    method set_coordinates (line 58) | def set_coordinates
    method trip_params (line 65) | def trip_params

FILE: app/controllers/users/digests_controller.rb
  class Users::DigestsController (line 3) | class Users::DigestsController < ApplicationController
    method index (line 11) | def index
    method show (line 16) | def show
    method create (line 21) | def create
    method destroy (line 34) | def destroy
    method set_digest (line 42) | def set_digest
    method available_years_for_generation (line 48) | def available_years_for_generation
    method valid_year? (line 55) | def valid_year?(year)

FILE: app/controllers/users/omniauth_callbacks_controller.rb
  class Users::OmniauthCallbacksController (line 3) | class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksCont...
    method github (line 4) | def github
    method google_oauth2 (line 8) | def google_oauth2
    method openid_connect (line 12) | def openid_connect
    method failure (line 16) | def failure
    method handle_auth (line 44) | def handle_auth(provider)
    method oidc_auto_register_enabled? (line 64) | def oidc_auto_register_enabled?

FILE: app/controllers/users/registrations_controller.rb
  class Users::RegistrationsController (line 3) | class Users::RegistrationsController < Devise::RegistrationsController
    method new (line 10) | def new
    method create (line 20) | def create
    method destroy (line 30) | def destroy
    method after_sign_up_path_for (line 48) | def after_sign_up_path_for(resource)
    method after_inactive_sign_up_path_for (line 54) | def after_inactive_sign_up_path_for(resource)
    method check_registration_allowed (line 62) | def check_registration_allowed
    method set_invitation (line 80) | def set_invitation
    method self_hosted_mode? (line 86) | def self_hosted_mode?
    method valid_invitation_token? (line 90) | def valid_invitation_token?
    method invitation_token (line 94) | def invitation_token
    method accept_invitation_for_user (line 100) | def accept_invitation_for_user(user)
    method sign_up_params (line 120) | def sign_up_params
    method store_signup_intent (line 124) | def store_signup_intent(user)
    method email_password_registration_allowed? (line 135) | def email_password_registration_allowed?
    method oidc_only_mode? (line 139) | def oidc_only_mode?

FILE: app/controllers/users/sessions_controller.rb
  class Users::SessionsController (line 3) | class Users::SessionsController < Devise::SessionsController
    method new (line 7) | def new
    method check_email_password_login_allowed (line 13) | def check_email_password_login_allowed
    method load_invitation_context (line 20) | def load_invitation_context
    method invitation_token (line 28) | def invitation_token

FILE: app/controllers/visits_controller.rb
  class VisitsController (line 3) | class VisitsController < ApplicationController
    method index (line 9) | def index
    method update (line 23) | def update
    method set_visit (line 65) | def set_visit
    method update_visit_name_from_place (line 69) | def update_visit_name_from_place
    method visit_params (line 74) | def visit_params

FILE: app/helpers/application_helper.rb
  type ApplicationHelper (line 3) | module ApplicationHelper
    function show_plan_data_window_alert? (line 4) | def show_plan_data_window_alert?
    function year_timespan (line 8) | def year_timespan(year)
    function header_colors (line 15) | def header_colors
    function new_version_available? (line 19) | def new_version_available?
    function app_theme (line 23) | def app_theme
    function active_class? (line 27) | def active_class?(link_path)
    function full_title (line 31) | def full_title(page_title = '')
    function active_tab? (line 36) | def active_tab?(link_path)
    function active_visit_places_tab? (line 40) | def active_visit_places_tab?(controller_name)
    function notification_link_color (line 44) | def notification_link_color(notification)
    function speed_text_color (line 50) | def speed_text_color(speed)
    function point_speed (line 56) | def point_speed(speed, unit = 'km')
    function speed_label (line 63) | def speed_label(unit = 'km')
    function onboarding_modal_showable? (line 67) | def onboarding_modal_showable?(user)
    function trial_button_class (line 71) | def trial_button_class(user)
    function trial_days_remaining_compact (line 84) | def trial_days_remaining_compact(user)
    function oauth_provider_name (line 92) | def oauth_provider_name(provider)
    function oauth_button_config (line 111) | def oauth_button_config(provider)
    function email_password_registration_enabled? (line 129) | def email_password_registration_enabled?
    function email_password_login_enabled? (line 135) | def email_password_login_enabled?
    function preferred_map_path (line 141) | def preferred_map_path(params = {})
    function upgrade_url (line 151) | def upgrade_url(utm_source: 'app', utm_medium: nil, utm_campaign: 'lit...
    function pro_badge_tag (line 157) | def pro_badge_tag(preview: true)
    function sortable_column (line 171) | def sortable_column(title, column, path_helper, **path_params)

FILE: app/helpers/country_flag_helper.rb
  type CountryFlagHelper (line 3) | module CountryFlagHelper
    function country_flag (line 4) | def country_flag(country_name)
    function country_to_code (line 16) | def country_to_code(country_name)

FILE: app/helpers/datetime_formatting_helper.rb
  type DatetimeFormattingHelper (line 3) | module DatetimeFormattingHelper
    function human_date (line 4) | def human_date(date)
    function human_datetime (line 8) | def human_datetime(datetime)
    function human_datetime_with_seconds (line 19) | def human_datetime_with_seconds(datetime)
    function days_left (line 30) | def days_left(active_until)
    function format_duration_short (line 43) | def format_duration_short(seconds)

FILE: app/helpers/flash_helper.rb
  type FlashHelper (line 3) | module FlashHelper
    function flash_alert_class (line 4) | def flash_alert_class(type)
    function flash_icon (line 13) | def flash_icon(type)

FILE: app/helpers/insights_helper.rb
  type InsightsHelper (line 3) | module InsightsHelper
    function monthly_digest_title (line 6) | def monthly_digest_title(digest)
    function monthly_digest_distance (line 12) | def monthly_digest_distance(digest, user)
    function monthly_digest_active_days (line 20) | def monthly_digest_active_days(digest)
    function previous_month_link (line 26) | def previous_month_link(year, month, available_months)
    function next_month_link (line 35) | def next_month_link(year, month, available_months)
    function weekly_pattern_chart_data (line 44) | def weekly_pattern_chart_data(digest, user)
    function top_locations_from_digest (line 59) | def top_locations_from_digest(digest, limit = 3)
    function format_location_time (line 90) | def format_location_time(minutes)
    function first_time_visits_from_digest (line 106) | def first_time_visits_from_digest(digest)
    function generate_travel_insight (line 115) | def generate_travel_insight(time_of_day, day_of_week, seasonality)
    function format_activity_hours (line 124) | def format_activity_hours(seconds)
    function activity_statistics (line 138) | def activity_statistics(activity_breakdown)
    function accumulate_activity_stat (line 146) | def accumulate_activity_stat(stats, mode, duration)
    function empty_activity_stats (line 159) | def empty_activity_stats
    function activity_ratio (line 164) | def activity_ratio(active_seconds, sedentary_seconds)
    function activity_breakdown_present? (line 172) | def activity_breakdown_present?(activity_breakdown)
    function calculate_activity_level (line 179) | def calculate_activity_level(distance, levels)
    function activity_level_class (line 191) | def activity_level_class(level)
    function format_heatmap_distance (line 202) | def format_heatmap_distance(meters, unit)
    function heatmap_week_columns (line 218) | def heatmap_week_columns(year)
    function heatmap_month_labels (line 239) | def heatmap_month_labels(weeks, year)
    function country_code (line 267) | def country_code(country_name)

FILE: app/helpers/month_styling_helper.rb
  type MonthStylingHelper (line 3) | module MonthStylingHelper
    function month_icon (line 45) | def month_icon(stat)
    function month_color (line 49) | def month_color(stat)
    function month_gradient_classes (line 53) | def month_gradient_classes(stat)
    function month_bg_image (line 57) | def month_bg_image(stat)

FILE: app/helpers/points_helper.rb
  type PointsHelper (line 3) | module PointsHelper
    function link_to_date (line 4) | def link_to_date(timestamp)

FILE: app/helpers/stats_comparison_helper.rb
  type StatsComparisonHelper (line 3) | module StatsComparisonHelper
    function x_than_average_distance (line 4) | def x_than_average_distance(stat, average_distance_this_year)
    function x_than_previous_active_days (line 15) | def x_than_previous_active_days(stat, previous_stat)
    function x_than_previous_countries_visited (line 30) | def x_than_previous_countries_visited(stat, previous_stat)

FILE: app/helpers/stats_helper.rb
  type StatsHelper (line 3) | module StatsHelper
    function year_distance_stat (line 4) | def year_distance_stat(year_data, user)
    function countries_and_cities_stat_for_year (line 8) | def countries_and_cities_stat_for_year(year, stats)
    function countries_and_cities_stat_for_month (line 22) | def countries_and_cities_stat_for_month(stat)
    function distance_traveled (line 29) | def distance_traveled(user, stat)
    function active_days (line 36) | def active_days(stat)
    function countries_visited (line 43) | def countries_visited(stat)
    function peak_day (line 47) | def peak_day(stat)
    function quietest_week (line 60) | def quietest_week(stat)
    function collect_countries_and_cities (line 73) | def collect_countries_and_cities(year_stats)
    function group_toponyms_by_country (line 86) | def group_toponyms_by_country(year_stats)
    function normalize_country_name (line 104) | def normalize_country_name(name)
    function canonical_names (line 113) | def canonical_names
    function build_distance_by_date_hash (line 117) | def build_distance_by_date_hash(stat)
    function find_quietest_week_start_date (line 123) | def find_quietest_week_start_date(stat, distance_by_date)
    function format_week_range (line 142) | def format_week_range(start_date)

FILE: app/helpers/tags_helper.rb
  type TagsHelper (line 3) | module TagsHelper
    function random_tag_emoji (line 17) | def random_tag_emoji

FILE: app/helpers/trips_helper.rb
  type TripsHelper (line 3) | module TripsHelper
    function immich_search_url (line 4) | def immich_search_url(base_url, start_date, end_date)
    function photoprism_search_url (line 14) | def photoprism_search_url(base_url, start_date, _end_date)
    function photo_search_url (line 19) | def photo_search_url(source, settings, start_date, end_date)
    function trip_duration (line 28) | def trip_duration(trip)

FILE: app/helpers/user_helper.rb
  type UserHelper (line 3) | module UserHelper
    function api_key_qr_code (line 4) | def api_key_qr_code(user, size: 6)

FILE: app/helpers/users/digests_helper.rb
  type Users (line 3) | module Users
    type DigestsHelper (line 4) | module DigestsHelper
      function progress_color_for_index (line 10) | def progress_color_for_index(index)
      function city_progress_value (line 14) | def city_progress_value(city_count, max_cities)
      function max_cities_count (line 20) | def max_cities_count(toponyms)
      function distance_with_unit (line 26) | def distance_with_unit(distance_meters, unit)
      function distance_comparison_text (line 31) | def distance_comparison_text(distance_meters)
      function format_time_spent (line 43) | def format_time_spent(minutes)
      function yoy_change_class (line 58) | def yoy_change_class(change)
      function yoy_change_text (line 64) | def yoy_change_text(change)

FILE: app/javascript/channels/family_locations_channel.js
  method connected (line 13) | connected() {
  method disconnected (line 17) | disconnected() {
  method received (line 21) | received(data) {

FILE: app/javascript/controllers/activity_heatmap_controller.js
  method showTooltip (line 9) | showTooltip(event) {
  method hideTooltip (line 47) | hideTooltip() {
  method formatDate (line 52) | formatDate(dateStr) {
  method formatDistance (line 63) | formatDistance(distanceMeters) {

FILE: app/javascript/controllers/add_visit_controller.js
  method connect (line 16) | connect() {
  method disconnect (line 29) | disconnect() {
  method waitForMap (line 34) | waitForMap() {
  method setupAddVisitButton (line 81) | setupAddVisitButton() {
  method toggleAddVisitMode (line 110) | toggleAddVisitMode(button) {
  method enterAddVisitMode (line 120) | enterAddVisitMode(button) {
  method exitAddVisitMode (line 140) | exitAddVisitMode(button) {
  method onMapClick (line 169) | onMapClick(e) {
  method showVisitForm (line 194) | showVisitForm(lat, lng) {
  method handleFormSubmit (line 296) | async handleFormSubmit(event) {
  method addCreatedVisitToMap (line 374) | addCreatedVisitToMap(_visitData, latitude, longitude) {
  method ensureConfirmedVisitsLayerEnabled (line 418) | ensureConfirmedVisitsLayerEnabled() {
  method refreshVisitsLayer (line 452) | refreshVisitsLayer() {
  method cleanup (line 460) | cleanup() {

FILE: app/javascript/controllers/area_creation_v2_controller.js
  method connect (line 15) | connect() {
  method open (line 22) | open(center, radius) {
  method close (line 32) | close() {
  method onSubmitEnd (line 39) | onSubmitEnd(event) {

FILE: app/javascript/controllers/area_drawer_controller.js
  method connect (line 9) | connect() {
  method startDrawing (line 24) | startDrawing(map) {
  method cancelDrawing (line 70) | cancelDrawing() {
  method onClick (line 93) | onClick(e) {
  method onMouseMove (line 117) | onMouseMove(e) {
  method updateDrawing (line 129) | updateDrawing() {

FILE: app/javascript/controllers/area_selector_controller.js
  method connect (line 11) | connect() {
  method startSelection (line 20) | startSelection() {
  method cancelSelection (line 68) | cancelSelection() {
  method updateSelection (line 129) | updateSelection() {
  method getSelectionBounds (line 155) | getSelectionBounds() {

FILE: app/javascript/controllers/base_controller.js
  method initialize (line 16) | initialize() {

FILE: app/javascript/controllers/checkbox_select_all_controller.js
  method connect (line 7) | connect() {
  method toggleChildren (line 15) | toggleChildren() {
  method toggleParent (line 28) | toggleParent() {
  method updateDeleteButtonVisibility (line 37) | updateDeleteButtonVisibility() {

FILE: app/javascript/controllers/clipboard_controller.js
  method copy (line 11) | copy() {
  method showButtonFeedback (line 24) | showButtonFeedback() {

FILE: app/javascript/controllers/color_picker_controller.js
  method connect (line 11) | connect() {
  method updateFromPicker (line 18) | updateFromPicker(event) {
  method selectSwatch (line 24) | selectSwatch(event) {
  method updateColor (line 34) | updateColor(color, updatePicker = true) {
  method updateActiveSwatchWithColor (line 65) | updateActiveSwatchWithColor(color) {

FILE: app/javascript/controllers/datetime_controller.js
  method connect (line 11) | connect() {
  method validateDates (line 40) | validateDates(showPopup = false) {
  method updateCoordinates (line 66) | async updateCoordinates() {

FILE: app/javascript/controllers/emoji_picker_controller.js
  method connect (line 12) | connect() {
  method disconnect (line 17) | disconnect() {
  method toggle (line 22) | toggle(event) {
  method open (line 33) | open() {
  method close (line 42) | close() {
  method createPicker (line 47) | createPicker() {
  method onEmojiSelect (line 72) | onEmojiSelect(emoji) {
  method submitForm (line 99) | submitForm() {
  method clearEmoji (line 109) | clearEmoji(event) {
  method getTheme (line 125) | getTheme() {
  method setupKeyboardListeners (line 136) | setupKeyboardListeners() {
  method removeKeyboardListeners (line 141) | removeKeyboardListeners() {
  method handleKeydown (line 145) | handleKeydown(event) {
  method setupOutsideClickListener (line 165) | setupOutsideClickListener() {
  method removeOutsideClickListener (line 173) | removeOutsideClickListener() {
  method handleOutsideClick (line 179) | handleOutsideClick(event) {
  method removePicker (line 185) | removePicker() {

FILE: app/javascript/controllers/family_members_controller.js
  method connect (line 14) | connect() {
  method disconnect (line 21) | disconnect() {
  method waitForMap (line 26) | waitForMap() {
  method initializeFamilyFeatures (line 46) | initializeFamilyFeatures() {
  method createFamilyMarkers (line 71) | createFamilyMarkers() {
  method updateSingleMemberLocation (line 162) | updateSingleMemberLocation(locationData) {
  method isRecentUpdate (line 221) | isRecentUpdate(updatedAt) {
  method createSingleFamilyMarker (line 229) | createSingleFamilyMarker(location) {
  method createTooltipContent (line 275) | createTooltipContent(lastSeen, battery) {
  method createPopupContent (line 281) | createPopupContent(location, lastSeen) {
  method addToLayerControl (line 364) | addToLayerControl() {
  method updateMapsControllerLayerControl (line 372) | updateMapsControllerLayerControl() {
  method setupEventListeners (line 393) | setupEventListeners() {
  method setupLayerControlEvents (line 411) | setupLayerControlEvents() {
  method zoomToFitAllMembers (line 442) | zoomToFitAllMembers() {
  method startPeriodicRefresh (line 461) | startPeriodicRefresh() {
  method stopPeriodicRefresh (line 476) | stopPeriodicRefresh() {
  method updateFamilyLocations (line 484) | updateFamilyLocations(locations) {
  method refreshFamilyLocations (line 508) | async refreshFamilyLocations() {
  method showFlashMessageToUser (line 560) | showFlashMessageToUser(type, message) {
  method manualRefreshFamilyLocations (line 565) | async manualRefreshFamilyLocations() {
  method cleanup (line 570) | cleanup() {
  method getFamilyMarkersLayer (line 598) | getFamilyMarkersLayer() {
  method isFamilyFeatureEnabled (line 603) | isFamilyFeatureEnabled() {
  method getFamilyMemberCount (line 608) | getFamilyMemberCount() {

FILE: app/javascript/controllers/family_navbar_indicator_controller.js
  method connect (line 9) | connect() {
  method disconnect (line 24) | disconnect() {
  method handleSharingUpdate (line 35) | handleSharingUpdate(event) {
  method handleSharingExpired (line 42) | handleSharingExpired(_event) {
  method updateIndicator (line 47) | updateIndicator() {

FILE: app/javascript/controllers/flash_controller.js
  constant ALERT_CLASSES (line 3) | const ALERT_CLASSES = {
  constant ICON_PATHS (line 12) | const ICON_PATHS = {
  constant CLOSE_PATH (line 22) | const CLOSE_PATH = "M6 18L18 6M6 6l12 12"
  method show (line 25) | static show(type, message) {

FILE: app/javascript/controllers/location_sharing_toggle_controller.js
  method connect (line 26) | connect() {
  method disconnect (line 30) | disconnect() {
  method toggle (line 34) | toggle() {
  method changeDuration (line 45) | changeDuration() {
  method toggleHistory (line 53) | toggleHistory() {
  method changeHistoryWindow (line 67) | changeHistoryWindow() {
  method setupExpirationTimer (line 83) | setupExpirationTimer() {
  method clearExpirationTimer (line 115) | clearExpirationTimer() {
  method updateExpirationCountdown (line 126) | updateExpirationCountdown() {

FILE: app/javascript/controllers/map_controls_controller.js
  method connect (line 6) | connect() {
  method toggle (line 14) | toggle() {
  method showPanel (line 26) | showPanel() {
  method hidePanel (line 36) | hidePanel() {

FILE: app/javascript/controllers/map_panel_controller.js
  method connect (line 20) | connect() {
  method switchTab (line 27) | switchTab(event) {
  method switchToTab (line 37) | switchToTab(tabName) {
  method activateTab (line 44) | activateTab(tabName) {

FILE: app/javascript/controllers/map_preview_controller.js
  method connect (line 10) | connect() {
  method initializeMap (line 20) | initializeMap() {
  method updatePreview (line 32) | updatePreview() {

FILE: app/javascript/controllers/maps/maplibre/area_selection_manager.js
  class AreaSelectionManager (line 11) | class AreaSelectionManager {
    method constructor (line 12) | constructor(controller) {
    method startSelectArea (line 25) | async startSelectArea() {
    method handleAreaSelected (line 73) | async handleAreaSelected(bounds) {
    method displaySelectedVisits (line 152) | displaySelectedVisits(visits) {
    method attachVisitCardListeners (line 186) | attachVisitCardListeners() {
    method updateBulkActions (line 223) | updateBulkActions() {
    method confirmVisit (line 297) | async confirmVisit(visitId) {
    method declineVisit (line 311) | async declineVisit(visitId) {
    method bulkMergeVisits (line 324) | async bulkMergeVisits() {
    method bulkConfirmVisits (line 352) | async bulkConfirmVisits() {
    method bulkDeclineVisits (line 370) | async bulkDeclineVisits() {
    method replaceVisitsWithMerged (line 393) | replaceVisitsWithMerged(oldVisitIds, mergedVisit) {
    method refreshSelectedVisits (line 451) | async refreshSelectedVisits() {
    method cancelAreaSelection (line 478) | cancelAreaSelection() {
    method deleteSelectedPoints (line 527) | async deleteSelectedPoints() {

FILE: app/javascript/controllers/maps/maplibre/data_loader.js
  class LoadingCounter (line 11) | class LoadingCounter {
    method constructor (line 12) | constructor(onUpdate) {
    method expect (line 23) | expect(source) {
    method update (line 28) | update(source, count) {
    method complete (line 33) | complete(source) {
    method isComplete (line 38) | isComplete() {
    method _report (line 45) | _report() {
  class DataLoader (line 61) | class DataLoader {
    method constructor (line 62) | constructor(api, apiKey, settings = {}) {
    method updateSettings (line 71) | updateSettings(settings) {
    method fetchPointsData (line 79) | async fetchPointsData(startDate, endDate) {
    method fetchMapData (line 117) | async fetchMapData(
    method visitsToGeoJSON (line 355) | visitsToGeoJSON(visits) {
    method photosToGeoJSON (line 380) | photosToGeoJSON(photos) {
    method placesToGeoJSON (line 414) | placesToGeoJSON(places) {
    method areasToGeoJSON (line 442) | areasToGeoJSON(areas) {
    method tracksToGeoJSON (line 471) | tracksToGeoJSON(tracks) {

FILE: app/javascript/controllers/maps/maplibre/date_manager.js
  class DateManager (line 4) | class DateManager {
    method formatDateForAPI (line 9) | static formatDateForAPI(date) {
    method parseMonthSelector (line 29) | static parseMonthSelector(value) {

FILE: app/javascript/controllers/maps/maplibre/event_handlers.js
  class EventHandlers (line 16) | class EventHandlers {
    method constructor (line 17) | constructor(map, controller) {
    method handlePointClick (line 34) | handlePointClick(e) {
    method handleTrackPointClick (line 46) | handleTrackPointClick(e) {
    method _buildPointInfoContent (line 70) | _buildPointInfoContent(properties) {
    method handleVisitClick (line 85) | handleVisitClick(e) {
    method handlePhotoClick (line 132) | handlePhotoClick(e) {
    method handlePlaceClick (line 149) | handlePlaceClick(e) {
    method handleAreaClick (line 182) | handleAreaClick(e) {
    method handleRouteHover (line 215) | handleRouteHover(e) {
    method handleRouteMouseLeave (line 256) | handleRouteMouseLeave() {
    method _getFullRouteFeature (line 277) | _getFullRouteFeature(properties) {
    method _areFeaturesSame (line 333) | _areFeaturesSame(feature1, feature2) {
    method _createRouteMarkers (line 359) | _createRouteMarkers(features) {
    method _createEmojiMarker (line 397) | _createEmojiMarker(emoji, markerClass = "route-emoji-marker") {
    method _clearRouteMarkers (line 411) | _clearRouteMarkers() {
    method handleRouteClick (line 421) | handleRouteClick(e) {
    method clearRouteSelection (line 486) | clearRouteSelection() {
    method handleTrackClick (line 506) | handleTrackClick(e) {
    method _loadTrackSegments (line 563) | async _loadTrackSegments(trackId, fullFeature) {
    method _showTrackInfoPanel (line 630) | _showTrackInfoPanel(properties) {
    method _buildTrackInfoContent (line 654) | _buildTrackInfoContent(
    method _updateSegmentsList (line 716) | _updateSegmentsList(segments) {
    method clearTrackSelection (line 759) | clearTrackSelection() {
    method _setupTrackPointsToggle (line 799) | _setupTrackPointsToggle() {
    method _toggleTrackPoints (line 823) | async _toggleTrackPoints(trackId, enabled) {
    method _clearTrackPointsLayer (line 863) | _clearTrackPointsLayer() {
    method _setMainPointsOpacity (line 878) | _setMainPointsOpacity(opacity) {
    method _getFullTrackFeature (line 893) | _getFullTrackFeature(properties) {
    method _createTrackSegmentMarkers (line 920) | _createTrackSegmentMarkers(feature, segments) {
    method _clearTrackMarkers (line 954) | _clearTrackMarkers() {
    method updateTrackMarkers (line 966) | updateTrackMarkers(feature) {
    method _cleanupSegmentListHover (line 992) | _cleanupSegmentListHover() {
    method _setupSegmentListHover (line 1010) | _setupSegmentListHover(segments) {
    method _zoomToSegment (line 1054) | _zoomToSegment(segment) {
    method _highlightSegmentOnMap (line 1085) | _highlightSegmentOnMap(segmentIndex) {
    method _clearSegmentHighlight (line 1110) | _clearSegmentHighlight() {
    method _highlightSegmentListItem (line 1126) | _highlightSegmentListItem(segmentIndex) {
    method _clearSegmentListHighlight (line 1140) | _clearSegmentListHighlight() {

FILE: app/javascript/controllers/maps/maplibre/filter_manager.js
  class FilterManager (line 4) | class FilterManager {
    method constructor (line 5) | constructor(dataLoader) {
    method setAllVisits (line 14) | setAllVisits(visits) {
    method filterAndUpdateVisits (line 21) | filterAndUpdateVisits(searchTerm, statusFilter, visitsLayer) {
    method getCurrentVisitFilter (line 45) | getCurrentVisitFilter() {
    method setCurrentVisitFilter (line 52) | setCurrentVisitFilter(filter) {

FILE: app/javascript/controllers/maps/maplibre/layer_manager.js
  class LayerManager (line 19) | class LayerManager {
    method constructor (line 20) | constructor(map, settings, api) {
    method addAllLayers (line 31) | async addAllLayers(
    method setupLayerEventHandlers (line 75) | setupLayerEventHandlers(handlers) {
    method toggleLayer (line 177) | toggleLayer(layerName) {
    method getLayer (line 188) | getLayer(layerName) {
    method registerLayer (line 197) | registerLayer(layerName, layerInstance) {
    method clearLayerReferences (line 204) | clearLayerReferences() {
    method _addScratchLayer (line 215) | async _addScratchLayer(pointsGeoJSON) {
    method _addHeatmapLayer (line 232) | _addHeatmapLayer(pointsGeoJSON) {
    method _addAreasLayer (line 243) | _addAreasLayer(areasGeoJSON) {
    method _addTracksLayer (line 254) | _addTracksLayer(tracksGeoJSON) {
    method _addRoutesLayer (line 265) | _addRoutesLayer(routesGeoJSON) {
    method _addRoutesHitLayer (line 276) | _addRoutesHitLayer() {
    method _addVisitsLayer (line 309) | _addVisitsLayer(visitsGeoJSON) {
    method _addPlacesLayer (line 320) | _addPlacesLayer(placesGeoJSON) {
    method _addPhotosLayer (line 331) | async _addPhotosLayer(photosGeoJSON) {
    method _addFamilyLayer (line 351) | _addFamilyLayer() {
    method _addPointsLayer (line 360) | _addPointsLayer(pointsGeoJSON) {
    method _addRecentPointLayer (line 373) | _addRecentPointLayer() {
    method _addReplayMarkerLayer (line 385) | _addReplayMarkerLayer() {
    method _addFogLayer (line 397) | _addFogLayer(pointsGeoJSON) {

FILE: app/javascript/controllers/maps/maplibre/map_data_manager.js
  constant EMPTY_GEOJSON (line 7) | const EMPTY_GEOJSON = { type: "FeatureCollection", features: [] }
  class MapDataManager (line 12) | class MapDataManager {
    method constructor (line 13) | constructor(controller) {
    method loadMapData (line 30) | async loadMapData(startDate, endDate, options = {}) {
    method ensurePointsLoaded (line 128) | async ensurePointsLoaded() {
    method _loadPoints (line 140) | async _loadPoints() {
    method _updateLayerBySource (line 181) | _updateLayerBySource(source, geoJSON) {
    method _updateTracksLayer (line 216) | _updateTracksLayer(tracksGeoJSON) {
    method _updatePhotosLayer (line 230) | _updatePhotosLayer(photosGeoJSON) {
    method _setupLayers (line 244) | async _setupLayers(data) {
    method _fitToFirstAvailable (line 324) | _fitToFirstAvailable(geojsonSources) {
    method _fitMapToBounds (line 338) | _fitMapToBounds(geojson) {
    method _showDataWindowBanner (line 374) | _showDataWindowBanner() {

FILE: app/javascript/controllers/maps/maplibre/map_initializer.js
  class MapInitializer (line 7) | class MapInitializer {
    method initialize (line 14) | static async initialize(container, settings = {}) {
    method fitToBounds (line 75) | static fitToBounds(map, geojson, options = {}) {

FILE: app/javascript/controllers/maps/maplibre/places_manager.js
  class PlacesManager (line 8) | class PlacesManager {
    method constructor (line 9) | constructor(controller) {
    method togglePlaces (line 20) | async togglePlaces(event) {
    method initializePlaceTagFilters (line 62) | async initializePlaceTagFilters() {
    method restoreSavedTagFilters (line 75) | restoreSavedTagFilters(savedFilters) {
    method enableAllTagsInitial (line 112) | enableAllTagsInitial() {
    method getSelectedPlaceTags (line 145) | getSelectedPlaceTags() {
    method filterPlacesByTags (line 157) | filterPlacesByTags(event) {
    method syncEnableAllTagsToggle (line 181) | syncEnableAllTagsToggle() {
    method loadPlacesWithTags (line 195) | async loadPlacesWithTags(tagIds = []) {
    method toggleAllPlaceTags (line 217) | toggleAllPlaceTags(event) {
    method startCreatePlace (line 250) | startCreatePlace() {
    method handlePlaceCreated (line 279) | async handlePlaceCreated(_event) {
    method handlePlaceUpdated (line 309) | async handlePlaceUpdated(event) {

FILE: app/javascript/controllers/maps/maplibre/routes_manager.js
  class RoutesManager (line 10) | class RoutesManager {
    method constructor (line 11) | constructor(controller) {
    method toggleRoutes (line 21) | async toggleRoutes(event) {
    method toggleSpeedColoredRoutes (line 46) | async toggleSpeedColoredRoutes(event) {
    method openSpeedColorEditor (line 64) | openSpeedColorEditor() {
    method createSpeedColorEditorModal (line 94) | createSpeedColorEditorModal(currentScale) {
    method handleSpeedColorSave (line 173) | handleSpeedColorSave(event) {
    method reloadRoutes (line 188) | async reloadRoutes() {
    method toggleHeatmap (line 241) | async toggleHeatmap(event) {
    method toggleFog (line 277) | async toggleFog(event) {
    method toggleScratch (line 313) | async toggleScratch(event) {
    method togglePhotos (line 371) | async togglePhotos(event) {
    method toggleAreas (line 431) | async toggleAreas(event) {
    method toggleTracks (line 472) | async toggleTracks(event) {
    method togglePoints (line 523) | async togglePoints(event) {
    method toggleFamily (line 542) | async toggleFamily(event) {

FILE: app/javascript/controllers/maps/maplibre/settings_manager.js
  constant RECALCULATION_POLL_INTERVAL (line 8) | const RECALCULATION_POLL_INTERVAL = 5000
  class SettingsController (line 14) | class SettingsController {
    method constructor (line 15) | constructor(controller) {
    method map (line 24) | get map() {
    method layerManager (line 28) | get layerManager() {
    method loadSettings (line 35) | async loadSettings() {
    method syncToggleStates (line 50) | syncToggleStates() {
    method syncTransportationSettings (line 249) | async syncTransportationSettings() {
    method resetTransportationDirtyState (line 438) | resetTransportationDirtyState() {
    method updateTransportationApplyButton (line 446) | updateTransportationApplyButton() {
    method markTransportationSettingsDirty (line 480) | markTransportationSettingsDirty() {
    method applyTransportationSettings (line 488) | async applyTransportationSettings() {
    method saveTransportationThresholds (line 507) | async saveTransportationThresholds() {
    method checkRecalculationStatus (line 623) | async checkRecalculationStatus() {
    method updateRecalculationUI (line 661) | updateRecalculationUI(status) {
    method setTransportationSettingsLocked (line 737) | setTransportationSettingsLocked(locked) {
    method startRecalculationPolling (line 834) | startRecalculationPolling() {
    method stopRecalculationPolling (line 845) | stopRecalculationPolling() {
    method toggleTransportationExpertMode (line 855) | toggleTransportationExpertMode(event) {
    method updateTransportationThresholdDisplay (line 874) | updateTransportationThresholdDisplay(event) {
    method getDistanceUnit (line 945) | getDistanceUnit() {
    method toDisplaySpeed (line 956) | toDisplaySpeed(kmh, isMetric) {
    method toMetricSpeed (line 967) | toMetricSpeed(value, isMetric) {
    method toDisplayDistance (line 978) | toDisplayDistance(km, isMetric) {
    method toMetricDistance (line 989) | toMetricDistance(value, isMetric) {
    method updateMapStyle (line 997) | async updateMapStyle(event) {
    method resetSettings (line 1017) | resetSettings() {
    method toggleGlobe (line 1028) | async toggleGlobe(event) {
    method updateRouteOpacity (line 1058) | updateRouteOpacity(event) {
    method updateAdvancedSettings (line 1072) | async updateAdvancedSettings(event) {
    method applySettingsToMap (line 1196) | async applySettingsToMap(settings) {
    method updateFogRadiusDisplay (line 1237) | updateFogRadiusDisplay(event) {
    method updateFogThresholdDisplay (line 1243) | updateFogThresholdDisplay(event) {
    method updateMetersBetweenDisplay (line 1249) | updateMetersBetweenDisplay(event) {
    method updateMinutesBetweenDisplay (line 1255) | updateMinutesBetweenDisplay(event) {
    method updateMinMinutesInCityDisplay (line 1261) | updateMinMinutesInCityDisplay(event) {
    method updateMaxGapMinutesDisplay (line 1267) | updateMaxGapMinutesDisplay(event) {

FILE: app/javascript/controllers/maps/maplibre/visits_manager.js
  class VisitsManager (line 8) | class VisitsManager {
    method constructor (line 9) | constructor(controller) {
    method toggleVisits (line 21) | async toggleVisits(event) {
    method searchVisits (line 68) | searchVisits(event) {
    method filterVisits (line 81) | filterVisits(event) {
    method startCreateVisit (line 93) | startCreateVisit() {
    method openVisitCreationModal (line 116) | openVisitCreationModal(lat, lng) {
    method handleVisitCreated (line 142) | async handleVisitCreated(_event) {
    method handleVisitUpdated (line 174) | async handleVisitUpdated(event) {

FILE: app/javascript/controllers/maps/maplibre_controller.js
  method connect (line 165) | async connect() {
  method disconnect (line 316) | disconnect() {
  method isWebGLSupported (line 327) | isWebGLSupported() {
  method showWebGLError (line 336) | showWebGLError() {
  method initializeMap (line 344) | async initializeMap() {
  method initializeAPI (line 354) | initializeAPI() {
  method initializeSearch (line 361) | initializeSearch() {
  method loadMapData (line 379) | async loadMapData(options = {}) {
  method monthChanged (line 390) | monthChanged(event) {
  method debouncedLoadFamilyHistory (line 403) | debouncedLoadFamilyHistory() {
  method showProgress (line 411) | showProgress() {
  method hideProgress (line 425) | hideProgress() {
  method showLoading (line 434) | showLoading() {
  method hideLoading (line 441) | hideLoading() {
  method updateLoadingCounts (line 448) | updateLoadingCounts({ counts, isComplete }) {
  method _renderLoadingBadge (line 457) | _renderLoadingBadge(isComplete = false) {
  method toggleSettings (line 501) | toggleSettings() {
  method handleTabChanged (line 510) | handleTabChanged(event) {
  method loadTimelineFeed (line 524) | loadTimelineFeed() {
  method refreshTimelineFeedIfActive (line 543) | refreshTimelineFeedIfActive() {
  method handleDayExpanded (line 558) | handleDayExpanded(event) {
  method handleDayCollapsed (line 582) | handleDayCollapsed() {
  method _applyDayHighlight (line 593) | _applyDayHighlight(day) {
  method _clearDayHighlight (line 648) | _clearDayHighlight() {
  method handleEntryHover (line 669) | handleEntryHover(event) {
  method handleEntryUnhover (line 748) | handleEntryUnhover() {
  method handleEntryClick (line 773) | handleEntryClick(event) {
  method handleEntryDeselect (line 815) | handleEntryDeselect() {
  method _dayRangeExpr (line 837) | _dayRangeExpr(property, rangeStart, rangeEnd, fullOpacity, dimOpacity) {
  method _safeSetPaint (line 855) | _safeSetPaint(layerId, property, value) {
  method _showDayVisits (line 866) | async _showDayVisits(day) {
  method _hideDayVisits (line 915) | _hideDayVisits() {
  method _findTrackFeature (line 939) | _findTrackFeature(trackId, startedAt) {
  method updateMapStyle (line 969) | updateMapStyle(event) {
  method resetSettings (line 972) | resetSettings() {
  method updateRouteOpacity (line 975) | updateRouteOpacity(event) {
  method updateAdvancedSettings (line 978) | updateAdvancedSettings(event) {
  method updateFogRadiusDisplay (line 981) | updateFogRadiusDisplay(event) {
  method updateFogThresholdDisplay (line 984) | updateFogThresholdDisplay(event) {
  method updateMetersBetweenDisplay (line 987) | updateMetersBetweenDisplay(event) {
  method updateMinutesBetweenDisplay (line 990) | updateMinutesBetweenDisplay(event) {
  method updateMinMinutesInCityDisplay (line 993) | updateMinMinutesInCityDisplay(event) {
  method updateMaxGapMinutesDisplay (line 996) | updateMaxGapMinutesDisplay(event) {
  method toggleGlobe (line 999) | toggleGlobe(event) {
  method toggleTransportationExpertMode (line 1002) | toggleTransportationExpertMode(event) {
  method updateTransportationThresholdDisplay (line 1005) | updateTransportationThresholdDisplay(event) {
  method markTransportationSettingsDirty (line 1008) | markTransportationSettingsDirty(event) {
  method applyTransportationSettings (line 1011) | applyTransportationSettings(event) {
  method startSelectArea (line 1016) | startSelectArea() {
  method cancelAreaSelection (line 1019) | cancelAreaSelection() {
  method deleteSelectedPoints (line 1022) | deleteSelectedPoints() {
  method toggleVisits (line 1027) | toggleVisits(event) {
  method searchVisits (line 1030) | searchVisits(event) {
  method filterVisits (line 1033) | filterVisits(event) {
  method startCreateVisit (line 1036) | startCreateVisit() {
  method togglePlaces (line 1041) | togglePlaces(event) {
  method filterPlacesByTags (line 1044) | filterPlacesByTags(event) {
  method toggleAllPlaceTags (line 1047) | toggleAllPlaceTags(event) {
  method startCreatePlace (line 1050) | startCreatePlace() {
  method startCreateArea (line 1055) | startCreateArea() {
  method handleAreaCreated (line 1077) | async handleAreaCreated(_event) {
  method togglePoints (line 1125) | togglePoints(event) {
  method toggleRoutes (line 1128) | toggleRoutes(event) {
  method toggleHeatmap (line 1131) | toggleHeatmap(event) {
  method toggleFog (line 1134) | toggleFog(event) {
  method toggleScratch (line 1137) | toggleScratch(event) {
  method togglePhotos (line 1140) | togglePhotos(event) {
  method toggleAreas (line 1143) | toggleAreas(event) {
  method toggleTracks (line 1146) | toggleTracks(event) {
  method toggleSpeedColoredRoutes (line 1149) | toggleSpeedColoredRoutes(event) {
  method openSpeedColorEditor (line 1152) | openSpeedColorEditor() {
  method handleSpeedColorSave (line 1155) | handleSpeedColorSave(event) {
  method toggleFamily (line 1158) | toggleFamily(event) {
  method loadFamilyMembers (line 1163) | async loadFamilyMembers() {
  method loadFamilyHistory (line 1220) | async loadFamilyHistory() {
  method updateFamilyInfoLines (line 1264) | updateFamilyInfoLines(historyMembers) {
  method renderFamilyMembersList (line 1289) | renderFamilyMembersList(locations) {
  method getFamilyMemberColor (line 1356) | getFamilyMemberColor(userId) {
  method centerOnFamilyMember (line 1373) | centerOnFamilyMember(event) {
  method showInfo (line 1385) | showInfo(title, content, actions = []) {
  method showRouteInfo (line 1423) | showRouteInfo(routeData) {
  method closeInfo (line 1469) | closeInfo() {
  method handleEdit (line 1487) | handleEdit(event) {
  method handleDelete (line 1507) | handleDelete(event) {
  method openVisitModal (line 1524) | async openVisitModal(visitId) {
  method deleteArea (line 1554) | async deleteArea(areaId) {
  method openPlaceEditModal (line 1592) | async openPlaceEditModal(placeId) {
  method switchToToolsTab (line 1619) | switchToToolsTab() {
  method toggleReplay (line 1643) | async toggleReplay() {
  method replayTrack (line 1665) | async replayTrack(event) {
  method _initializeReplay (line 1723) | async _initializeReplay() {
  method _getLoadedPoints (line 1766) | _getLoadedPoints() {
  method _setInitialScrubberPosition (line 1785) | _setInitialScrubberPosition() {
  method replayScrubberHover (line 1803) | replayScrubberHover(event) {
  method _handleReplayMinuteChange (line 1812) | _handleReplayMinuteChange(minute) {
  method _jumpReplayToMinute (line 1870) | _jumpReplayToMinute(minute) {
  method replayPrevDay (line 1912) | replayPrevDay() {
  method replayNextDay (line 1933) | replayNextDay() {
  method replayCyclePrev (line 1954) | replayCyclePrev() {
  method replayCycleNext (line 1973) | replayCycleNext() {
  method _updateReplayDayDisplay (line 1993) | _updateReplayDayDisplay() {
  method _updateReplayDayButtons (line 2003) | _updateReplayDayButtons() {
  method _updateReplayTimeDisplay (line 2021) | _updateReplayTimeDisplay(minute, showNoData = false) {
  method _getPointVelocity (line 2044) | _getPointVelocity(point) {
  method _updateReplaySpeedDisplay (line 2062) | _updateReplaySpeedDisplay(velocity) {
  method _updateReplayDayCount (line 2089) | _updateReplayDayCount() {
  method _renderReplayDensity (line 2103) | _renderReplayDensity() {
  method _updateReplayCycleControls (line 2137) | _updateReplayCycleControls(minute) {
  method _hideReplayCycleControls (line 2157) | _hideReplayCycleControls() {
  method replayTogglePlayback (line 2168) | replayTogglePlayback() {
  method replaySpeedChange (line 2179) | replaySpeedChange(event) {
  method _startReplayPlayback (line 2193) | _startReplayPlayback() {
  method _stopReplayPlayback (line 2262) | _stopReplayPlayback() {
  method _updateTrackReplayButton (line 2294) | _updateTrackReplayButton(playing) {
  method _replayFrame (line 2315) | _replayFrame() {
  method _panMapToFollowMarker (line 2428) | _panMapToFollowMarker(lon, lat) {
  method _showReplayMarkerAt (line 2454) | _showReplayMarkerAt(lon, lat) {
  method _initializeReplayState (line 2467) | _initializeReplayState() {
  method _showReplayMarker (line 2489) | _showReplayMarker(point) {
  method _clearReplayMarker (line 2505) | _clearReplayMarker() {
  method _flyToReplayPoint (line 2518) | _flyToReplayPoint(point, fast = false) {
  method _highlightReplayRouteSegment (line 2533) | _highlightReplayRouteSegment(point) {
  method _parseReplayTimestamp (line 2580) | _parseReplayTimestamp(timestamp) {
  method _clearReplayRouteHighlight (line 2605) | _clearReplayRouteHighlight() {

FILE: app/javascript/controllers/maps/maplibre_realtime_controller.js
  method connect (line 18) | connect() {
  method disconnect (line 47) | disconnect() {
  method setupChannels (line 56) | setupChannels() {
  method toggleLiveMode (line 74) | toggleLiveMode(event) {
  method updateRecentPointLayerVisibility (line 95) | updateRecentPointLayerVisibility() {
  method handleConnected (line 118) | handleConnected(channelName) {
  method handleDisconnected (line 130) | handleDisconnected(channelName) {
  method handleReceived (line 142) | handleReceived(data) {
  method mapsV2Controller (line 159) | get mapsV2Controller() {
  method handleNewPoint (line 169) | handleNewPoint(pointData) {
  method handleFamilyLocation (line 231) | handleFamilyLocation(member) {
  method updateRecentPoint (line 247) | updateRecentPoint(longitude, latitude, properties = {}) {
  method zoomToPoint (line 270) | zoomToPoint(longitude, latitude) {
  method updateConnectionIndicator (line 290) | updateConnectionIndicator(_connected) {}

FILE: app/javascript/controllers/maps_controller.js
  method connect (line 64) | async connect() {
  method showDataWindowBanner (line 342) | showDataWindowBanner() {
  method disconnect (line 359) | disconnect() {
  method setupSubscription (line 378) | setupSubscription() {
  method setupTracksSubscription (line 391) | setupTracksSubscription() {
  method handleTrackUpdate (line 402) | handleTrackUpdate(data) {
  method initializeLiveMapHandler (line 432) | initializeLiveMapHandler() {
  method appendPoint (line 465) | appendPoint(data) {
  method initializeScratchLayer (line 477) | async initializeScratchLayer() {
  method toggleScratchLayer (line 487) | toggleScratchLayer() {
  method baseMaps (line 493) | baseMaps() {
  method createTreeLayerControl (line 542) | createTreeLayerControl(additionalLayers = {}) {
  method removeEventListeners (line 679) | removeEventListeners() {
  method addEventListeners (line 683) | addEventListeners() {
  method updatePreferredBaseLayer (line 850) | updatePreferredBaseLayer(selectedLayerName) {
  method saveEnabledLayers (line 876) | saveEnabledLayers() {
  method deletePoint (line 958) | deletePoint(id, apiKey) {
  method removeMarker (line 1029) | removeMarker(id) {
  method updateFog (line 1054) | updateFog(markers, clearFogRadius, fogLineThreshold) {
  method initializeDrawControl (line 1070) | initializeDrawControl() {
  method addSettingsButton (line 1110) | addSettingsButton() {
  method addInfoToggleButton (line 1151) | addInfoToggleButton() {
  method toggleFooterVisibility (line 1201) | toggleFooterVisibility() {
  method adjustBottomControls (line 1237) | adjustBottomControls(paddingBottom) {
  method toggleSettingsMenu (line 1262) | toggleSettingsMenu() {
  method pointsRenderingModeChecked (line 1434) | pointsRenderingModeChecked(value) {
  method liveMapEnabledChecked (line 1442) | liveMapEnabledChecked(value) {
  method speedColoredRoutesChecked (line 1450) | speedColoredRoutesChecked() {
  method updateSettings (line 1454) | updateSettings(event) {
  method updateMapWithNewSettings (line 1511) | updateMapWithNewSettings(newSettings) {
  method initializeTopRightButtons (line 1656) | initializeTopRightButtons() {
  method shouldShowTracksSelector (line 1688) | shouldShowTracksSelector() {
  method addRoutesTracksSelector (line 1693) | addRoutesTracksSelector() {
  method switchRouteMode (line 1754) | switchRouteMode(mode, _isInitial = false) {
  method updateLayerControlCheckboxes (line 1809) | updateLayerControlCheckboxes(layerName, isVisible) {
  method handleRouteLayerToggle (line 1827) | handleRouteLayerToggle(mode) {
  method updateRadioButtonState (line 1840) | updateRadioButtonState() {
  method updatePaneVisibilityAfterLayerChange (line 1857) | updatePaneVisibilityAfterLayerChange() {
  method initializeLayersFromSettings (line 1873) | initializeLayersFromSettings() {
  method updateTreeControlCheckboxes (line 2011) | updateTreeControlCheckboxes(enabledLayers) {
  method setupFamilyLayerListener (line 2077) | setupFamilyLayerListener() {
  method toggleRightPanel (line 2110) | toggleRightPanel() {
  method fetchAndDisplayTrackedMonths (line 2238) | async fetchAndDisplayTrackedMonths(
  method getWholeYearLink (line 2393) | getWholeYearLink() {
  method fetchAndDisplayVisitedCities (line 2416) | async fetchAndDisplayVisitedCities() {
  method displayVisitedCities (line 2461) | displayVisitedCities(citiesData) {
  method showGradientEditor (line 2499) | showGradientEditor() {
  method initializeTracksLayer (line 2639) | async initializeTracksLayer() {
  method createTracksFromData (line 2649) | createTracksFromData(tracksData) {
  method toggleTracksVisibility (line 2671) | toggleTracksVisibility(event) {
  method initializeLocationSearch (line 2679) | initializeLocationSearch() {
  method updateLayerControl (line 2690) | updateLayerControl(additionalLayers = {}) {
  method togglePlaceCreationMode (line 2701) | togglePlaceCreationMode() {
  method disablePlaceCreationMode (line 2729) | disablePlaceCreationMode() {
  method initializePrivacyZones (line 2746) | async initializePrivacyZones() {

FILE: app/javascript/controllers/notifications_controller.js
  method connect (line 4) | connect() {
  method disconnect (line 11) | disconnect() {
  method enforceLimit (line 18) | enforceLimit() {

FILE: app/javascript/controllers/onboarding_modal_controller.js
  method connect (line 12) | connect() {
  method disconnect (line 18) | disconnect() {
  method checkAndShowModal (line 28) | checkAndShowModal() {
  method showImport (line 43) | showImport() {
  method showTrack (line 48) | showTrack() {
  method showChoice (line 53) | showChoice() {
  method dismiss (line 57) | dismiss() {
  method switchScreen (line 61) | switchScreen(targetName) {
  method completeOnboarding (line 75) | completeOnboarding() {
  method trackEvent (line 92) | trackEvent(eventName) {

FILE: app/javascript/controllers/place_creation_controller.js
  method connect (line 18) | connect() {
  method setupEventListeners (line 24) | setupEventListeners() {
  method setupTagListeners (line 33) | setupTagListeners() {
  method open (line 56) | open(latitude, longitude) {
  method openForEdit (line 78) | openForEdit(place) {
  method close (line 115) | close() {
  method loadNearbyFrame (line 129) | loadNearbyFrame(latitude, longitude) {
  method selectNearby (line 135) | selectNearby(event) {
  method onSubmitEnd (line 142) | onSubmitEnd(event) {
  method addMethodOverride (line 164) | addMethodOverride(method) {
  method removeMethodOverride (line 175) | removeMethodOverride() {

FILE: app/javascript/controllers/places_filter_controller.js
  method connect (line 4) | connect() {
  method filterPlaces (line 8) | filterPlaces(_event) {
  method clearAll (line 32) | clearAll(event) {

FILE: app/javascript/controllers/privacy_radius_controller.js
  method toggleRadius (line 6) | toggleRadius(event) {
  method updateFromSlider (line 25) | updateFromSlider(event) {

FILE: app/javascript/controllers/public_stat_map_controller.js
  method connect (line 17) | connect() {
  method disconnect (line 26) | disconnect() {
  method initializeMap (line 32) | initializeMap() {
  method addMapLayers (line 50) | addMapLayers() {
  method addFallbackOSMLayer (line 74) | addFallbackOSMLayer() {
  method loadHexagons (line 81) | async loadHexagons() {
  method loadStaticHexagons (line 145) | async loadStaticHexagons() {
  method addStaticHexagonsToMap (line 233) | addStaticHexagonsToMap(geojsonData) {
  method styleHexagon (line 264) | styleHexagon() {
  method buildPopupContent (line 274) | buildPopupContent(props) {
  method onHexagonMouseOver (line 331) | onHexagonMouseOver(e) {
  method onHexagonMouseOut (line 349) | onHexagonMouseOut(e) {

FILE: app/javascript/controllers/removals_controller.js
  method connect (line 8) | connect() {
  method remove (line 16) | remove() {

FILE: app/javascript/controllers/sharing_modal_controller.js
  method toggleSharing (line 12) | toggleSharing() {
  method expirationChanged (line 27) | expirationChanged() {
  method copyLink (line 33) | async copyLink() {

FILE: app/javascript/controllers/speed_color_editor_controller.js
  method connect (line 13) | connect() {
  method loadColorStops (line 17) | loadColorStops() {
  method parseColorStops (line 26) | parseColorStops(stopsString) {
  method serializeColorStops (line 33) | serializeColorStops() {
  method renderStops (line 37) | renderStops() {
  method updateSpeed (line 91) | updateSpeed(event) {
  method updateColor (line 97) | updateColor(event) {
  method updateColorText (line 112) | updateColorText(event) {
  method addStop (line 131) | addStop() {
  method removeStop (line 148) | removeStop(event) {
  method updatePreview (line 158) | updatePreview() {
  method save (line 171) | save() {
  method close (line 182) | close() {
  method resetToDefault (line 191) | resetToDefault() {

FILE: app/javascript/controllers/stat_page_controller.js
  method connect (line 9) | connect() {
  method disconnect (line 35) | disconnect() {
  method initializeMap (line 42) | initializeMap() {
  method loadMonthData (line 85) | async loadMonthData() {
  method processPointsData (line 134) | processPointsData(points) {
  method toggleHeatmap (line 192) | toggleHeatmap() {
  method togglePoints (line 228) | togglePoints() {
  method showLoading (line 246) | showLoading(show) {
  method showError (line 252) | showError(message) {
  method showNoData (line 265) | showNoData() {
  method addMapLayers (line 278) | addMapLayers() {
  method addFallbackOSMLayer (line 302) | addFallbackOSMLayer() {

FILE: app/javascript/controllers/timeline_feed_controller.js
  method dayToggled (line 16) | dayToggled(event) {
  method toggleTrackInfo (line 56) | toggleTrackInfo(event) {
  method entryHover (line 106) | entryHover(event) {
  method entryUnhover (line 121) | entryUnhover() {

FILE: app/javascript/controllers/trip_map_controller.js
  method connect (line 18) | connect() {
  method initializeMap (line 26) | initializeMap() {
  method baseMaps (line 51) | baseMaps() {
  method showRoute (line 91) | showRoute() {
  method getCoordinates (line 115) | getCoordinates(pathData) {
  method disconnect (line 148) | disconnect() {

FILE: app/javascript/controllers/trips_controller.js
  method connect (line 17) | connect() {
  method initializeMap (line 41) | initializeMap() {
  method disconnect (line 171) | disconnect() {
  method baseMaps (line 177) | baseMaps() {
  method addMarkers (line 211) | addMarkers() {
  method addPolyline (line 229) | addPolyline() {
  method fitMapToBounds (line 242) | fitMapToBounds() {
  method updateMapWithCoordinates (line 250) | updateMapWithCoordinates(newCoordinates) {

FILE: app/javascript/controllers/upload_controller.js
  constant MAX_FILE_SIZE (line 5) | const MAX_FILE_SIZE = 11 * 1024 * 1024 // 11MB
  constant VALID_ZIP_TYPES (line 6) | const VALID_ZIP_TYPES = ["application/zip", "application/x-zip-compressed"]
  method connect (line 20) | connect() {
  method onSubmit (line 33) | onSubmit(event) {
  method upload (line 45) | upload() {
  method validateFiles (line 92) | validateFiles(files) {
  method createProgressBar (line 133) | createProgressBar() {
  method uploadComplete (line 149) | uploadComplete() {
  method markProgressComplete (line 170) | markProgressComplete() {
  method updateAggregateProgress (line 183) | updateAggregateProgress() {
  method addHiddenField (line 192) | addHiddenField(signedId) {
  method clearExistingHiddenFields (line 200) | clearExistingHiddenFields() {
  method hasUploadedFiles (line 208) | hasUploadedFiles() {
  method hiddenFieldCount (line 212) | hiddenFieldCount() {
  method disableSubmit (line 218) | disableSubmit() {

FILE: app/javascript/controllers/visit_creation_v2_controller.js
  method connect (line 24) | connect() {
  method setupEventListeners (line 32) | setupEventListeners() {
  method disconnect (line 38) | disconnect() {
  method open (line 45) | open(lat, lng, mapController) {
  method openForEdit (line 81) | openForEdit(visit) {
  method close (line 132) | close() {
  method submit (line 151) | async submit(event) {
  method addMarker (line 234) | addMarker(lat, lng) {
  method removeMarker (line 258) | removeMarker() {
  method cleanup (line 268) | cleanup() {
  method formatDateTime (line 275) | formatDateTime(date) {
  method showToast (line 282) | showToast(message, type = "info") {

FILE: app/javascript/controllers/visit_modal_map_controller.js
  method connect (line 11) | connect() {
  method addMarkers (line 31) | addMarkers() {

FILE: app/javascript/controllers/visit_modal_places_controller.js
  method selectPlace (line 6) | selectPlace() {

FILE: app/javascript/controllers/visit_name_controller.js
  method edit (line 6) | edit() {
  method save (line 12) | save() {
  method cancel (line 16) | cancel() {
  method handleEnter (line 21) | handleEnter(event) {

FILE: app/javascript/controllers/visits_map_controller.js
  method connect (line 8) | connect() {
  method initializeMap (line 14) | initializeMap() {
  method getVisitColor (line 58) | getVisitColor(element) {
  method highlightVisit (line 71) | highlightVisit(event) {
  method unhighlightVisit (line 98) | unhighlightVisit(event) {

FILE: app/javascript/maps/areas.js
  function handleAreaCreated (line 78) | function handleAreaCreated(areasLayer, layer, apiKey) {
  function saveArea (line 178) | function saveArea(formData, areasLayer, layer, apiKey) {
  function deleteArea (line 249) | function deleteArea(id, areasLayer, layer, apiKey) {
  function fetchAndDrawAreas (line 272) | function fetchAndDrawAreas(areasLayer, apiKey) {

FILE: app/javascript/maps/country_codes.js
  function countryCodesMap (line 1) | function countryCodesMap() {

FILE: app/javascript/maps/fog_of_war.js
  function initializeFogCanvas (line 1) | function initializeFogCanvas(map) {
  function drawFogCanvas (line 26) | function drawFogCanvas(map, markers, clearFogRadius, fogLineThreshold) {
  function metersToPixels (line 98) | function metersToPixels(map, meters) {
  function getMetersPerPixel (line 105) | function getMetersPerPixel(latitude, zoom) {
  function createFogOverlay (line 113) | function createFogOverlay() {

FILE: app/javascript/maps/helpers.js
  function formatDistance (line 2) | function formatDistance(distance, unit = "km") {
  function getUrlParameter (line 31) | function getUrlParameter(name) {
  function minutesToDaysHoursMinutes (line 35) | function minutesToDaysHoursMinutes(minutes) {
  function formatDate (line 56) | function formatDate(timestamp, timezone) {
  function formatSpeed (line 93) | function formatSpeed(speedKmh, unit = "km") {
  function haversineDistance (line 102) | function haversineDistance(lat1, lon1, lat2, lon2, unit = "km") {
  function debounce (line 124) | function debounce(func, wait) {

FILE: app/javascript/maps/layers.js
  function createMapLayer (line 7) | function createMapLayer(map, selectedLayerName, layerKey, selfHosted) {
  function createAllMapLayers (line 55) | function createAllMapLayers(map, selectedLayerName, selfHosted) {
  function osmMapLayer (line 77) | function osmMapLayer(map, selectedLayerName) {

FILE: app/javascript/maps/live_map_handler.js
  class LiveMapHandler (line 15) | class LiveMapHandler {
    method constructor (line 16) | constructor(map, layers, options = {}) {
    method enable (line 50) | enable() {
    method disable (line 58) | disable() {
    method enabled (line 67) | get enabled() {
    method appendPoint (line 76) | appendPoint(data) {
    method getStats (line 117) | getStats() {
    method updateOptions (line 130) | updateOptions(newOptions) {
    method clear (line 137) | clear() {
    method _enforcePointLimits (line 167) | _enforcePointLimits() {
    method _createMarker (line 183) | _createMarker(point) {
    method _updateHeatmap (line 191) | _updateHeatmap(point) {
    method _updatePolylines (line 209) | _updatePolylines(newPoint) {
    method _updateFogOfWar (line 234) | _updateFogOfWar() {
    method _updateLastMarker (line 246) | _updateLastMarker() {
    method _cleanup (line 264) | _cleanup() {

FILE: app/javascript/maps/location_search.js
  class LocationSearch (line 4) | class LocationSearch {
    method constructor (line 5) | constructor(map, apiKey, userTheme = "dark") {
    method initializeSearchBar (line 22) | initializeSearchBar() {
    method createInlineSearchBar (line 72) | createInlineSearchBar() {
    method preventMapScrollOnContainers (line 165) | preventMapScrollOnContainers() {
    method bindSearchEvents (line 230) | bindSearchEvents() {
    method showLoading (line 324) | showLoading() {
    method showError (line 337) | showError(message) {
    method displaySearchResults (line 351) | displaySearchResults(data) {
    method buildLocationResultHtml (line 386) | buildLocationResultHtml(location, index) {
    method groupVisitsByYear (line 446) | groupVisitsByYear(visits) {
    method formatDateShort (line 467) | formatDateShort(dateString) {
    method bindResultEvents (line 476) | bindResultEvents() {
    method toggleYear (line 500) | toggleYear(locationIndex, year, toggleElement) {
    method focusOnLocation (line 519) | focusOnLocation(location) {
    method focusOnVisit (line 540) | focusOnVisit(location, visitIndex) {
    method addVisitMarker (line 573) | addVisitMarker(lat, lon, visit, location) {
    method clearSearch (line 633) | clearSearch() {
    method clearVisitMarker (line 641) | clearVisitMarker() {
    method showSearchBar (line 660) | showSearchBar() {
    method repositionSearchBar (line 701) | repositionSearchBar() {
    method hideSearchBar (line 719) | hideSearchBar() {
    method showDefaultState (line 728) | showDefaultState() {
    method clearSearchMarkers (line 734) | clearSearchMarkers() {
    method hideResults (line 743) | hideResults() {
    method debouncedSuggestionSearch (line 750) | debouncedSuggestionSearch(query) {
    method performSuggestionSearch (line 762) | async performSuggestionSearch(query) {
    method showSuggestionsLoading (line 795) | showSuggestionsLoading() {
    method displaySuggestions (line 808) | displaySuggestions(suggestions) {
    method bindSuggestionEvents (line 839) | bindSuggestionEvents() {
    method navigateSuggestions (line 850) | navigateSuggestions(direction) {
    method highlightActiveSuggestion (line 872) | highlightActiveSuggestion() {
    method selectSuggestion (line 887) | selectSuggestion(index) {
    method showSearchLoading (line 898) | showSearchLoading(locationName) {
    method performCoordinateSearch (line 912) | async performCoordinateSearch(suggestion) {
    method hideSuggestions (line 946) | hideSuggestions() {
    method createVisitAt (line 958) | createVisitAt(lat, lon, placeName, visitDate, durationEstimate) {
    method showBasicVisitForm (line 977) | showBasicVisitForm(lat, lon, placeName, presetStartTime, presetEndTime) {
    method handleBasicFormSubmit (line 1089) | async handleBasicFormSubmit(event, popup) {
    method refreshVisitsIfAvailable (line 1155) | refreshVisitsIfAvailable() {
    method calculateVisitTimes (line 1173) | calculateVisitTimes(visitDate, durationEstimate) {
    method escapeHtml (line 1232) | escapeHtml(text) {
    method formatDate (line 1243) | formatDate(dateString) {
    method formatDateTime (line 1247) | formatDateTime(dateString) {

FILE: app/javascript/maps/map_controls.js
  function createStandardButton (line 15) | function createStandardButton(
  function createTogglePanelControl (line 61) | function createTogglePanelControl(onClickCallback, userTheme = "dark") {
  function createVisitsDrawerControl (line 92) | function createVisitsDrawerControl(onClickCallback, userTheme = "dark") {
  function createAreaSelectionControl (line 116) | function createAreaSelectionControl(
  function createAddVisitControl (line 145) | function createAddVisitControl(onClickCallback, userTheme = "dark") {
  function createCreatePlaceControl (line 169) | function createCreatePlaceControl(onClickCallback, userTheme = "dark") {
  function addTopRightButtons (line 204) | function addTopRightButtons(map, callbacks, userTheme = "dark") {
  function setAddVisitButtonActive (line 264) | function setAddVisitButtonActive(button) {
  function setAddVisitButtonInactive (line 277) | function setAddVisitButtonInactive(button, userTheme = "dark") {
  function setCreatePlaceButtonActive (line 289) | function setCreatePlaceButtonActive(button) {
  function setCreatePlaceButtonInactive (line 304) | function setCreatePlaceButtonInactive(button, userTheme = "dark") {

FILE: app/javascript/maps/marker_factory.js
  constant MARKER_DATA_INDICES (line 3) | const MARKER_DATA_INDICES = {
  function createStandardIcon (line 31) | function createStandardIcon(color = "blue", size = 4) {
  function createLiveMarker (line 48) | function createLiveMarker(point, options = {}) {
  function createInteractiveMarker (line 76) | function createInteractiveMarker(
  function createSimplifiedMarker (line 119) | function createSimplifiedMarker(point, userSettings = {}) {
  function addDragHandlers (line 164) | function addDragHandlers(marker, apiKey, userSettings) {

FILE: app/javascript/maps/markers.js
  function createMarkersArray (line 7) | function createMarkersArray(markersData, userSettings, apiKey) {
  function createSimplifiedMarkers (line 26) | function createSimplifiedMarkers(markersData, _renderer, userSettings) {

FILE: app/javascript/maps/photos.js
  function fetchAndDisplayPhotos (line 7) | async function fetchAndDisplayPhotos(
  function getPhotoLink (line 113) | function getPhotoLink(photo, userSettings) {
  function getSourceUrl (line 137) | function getSourceUrl(photo, userSettings) {
  function createPhotoMarker (line 148) | function createPhotoMarker(photo, userSettings, photoMarkers, apiKey) {

FILE: app/javascript/maps/places.js
  class PlacesManager (line 7) | class PlacesManager {
    method constructor (line 8) | constructor(map, apiKey) {
    method initialize (line 19) | async initialize() {
    method setupEventListeners (line 32) | setupEventListeners() {
    method loadPlaces (line 129) | async loadPlaces(tagIds = null, untaggedOnly = false) {
    method renderPlaces (line 155) | renderPlaces() {
    method createPlaceMarker (line 169) | createPlaceMarker(place) {
    method createPlaceIcon (line 184) | createPlaceIcon(place) {
    method createPopupContent (line 216) | createPopupContent(place) {
    method escapeHtml (line 251) | escapeHtml(text) {
    method sanitizeColor (line 257) | sanitizeColor(color) {
    method setupMapClickHandler (line 271) | setupMapClickHandler() {
    method handleMapClick (line 306) | async handleMapClick(e) {
    method triggerPlaceCreation (line 323) | async triggerPlaceCreation(lat, lng) {
    method editPlace (line 331) | editPlace(placeId) {
    method deletePlace (line 345) | async deletePlace(placeId) {
    method enableCreationMode (line 386) | enableCreationMode() {
    method disableCreationMode (line 392) | disableCreationMode() {
    method filterByTags (line 402) | filterByTags(tagIds, untaggedOnly = false) {
    method createFilteredLayer (line 411) | createFilteredLayer(tagIds) {
    method loadPlacesIntoLayer (line 428) | async loadPlacesIntoLayer(layer, tagIds) {
    method refreshPlaces (line 458) | async refreshPlaces() {
    method ensurePlacesLayerVisible (line 464) | ensurePlacesLayerVisible() {
    method show (line 503) | show() {
    method hide (line 509) | hide() {
    method showNotification (line 515) | showNotification(message, type = "info") {

FILE: app/javascript/maps/places_control.js
  function createPlacesControl (line 7) | function createPlacesControl(placesManager, tags, userTheme = "dark") {

FILE: app/javascript/maps/polylines.js
  function calculateSpeed (line 9) | function calculateSpeed(point1, point2) {
  function colorFormatEncode (line 44) | function colorFormatEncode(arr) {
  function colorFormatDecode (line 48) | function colorFormatDecode(str) {
  function getSpeedColor (line 55) | function getSpeedColor(speedKmh, useSpeedColors, speedColorScale) {
  function hexToRGB (line 96) | function hexToRGB(hex) {
  function processInBatches (line 104) | function processInBatches(items, batchSize, processFn) {
  function addHighlightOnHover (line 143) | function addHighlightOnHover(
  function createPolylinesLayer (line 396) | function createPolylinesLayer(
  function updatePolylinesColors (line 554) | function updatePolylinesColors(
  function updatePolylinesOpacity (line 600) | function updatePolylinesOpacity(polylinesLayer, opacity) {
  function reestablishPolylineEventHandlers (line 620) | function reestablishPolylineEventHandlers(
  function managePaneVisibility (line 713) | function managePaneVisibility(map, activeLayerType) {

FILE: app/javascript/maps/popups.js
  function createPopupContent (line 3) | function createPopupContent(marker, timezone, distanceUnit) {

FILE: app/javascript/maps/privacy_zones.js
  class PrivacyZoneManager (line 7) | class PrivacyZoneManager {
    method constructor (line 8) | constructor(map, apiKey) {
    method loadPrivacyZones (line 16) | async loadPrivacyZones() {
    method isPointInPrivacyZone (line 35) | isPointInPrivacyZone(lat, lng) {
    method filterPoints (line 52) | filterPoints(points) {
    method filterTracks (line 98) | filterTracks(tracks) {
    method showPrivacyCircles (line 117) | showPrivacyCircles() {
    method hidePrivacyCircles (line 152) | hidePrivacyCircles() {
    method togglePrivacyCircles (line 159) | togglePrivacyCircles(show = null) {
    method hasPrivacyZones (line 169) | hasPrivacyZones() {
    method getZoneCount (line 173) | getZoneCount() {
    method getTotalPlacesCount (line 177) | getTotalPlacesCount() {

FILE: app/javascript/maps/scratch_layer.js
  class ScratchLayer (line 3) | class ScratchLayer {
    method constructor (line 4) | constructor(map, markers, countryCodesMap, apiKey) {
    method setup (line 13) | async setup() {
    method _fetchWorldBordersData (line 56) | async _fetchWorldBordersData() {
    method getVisitedCountries (line 77) | getVisitedCountries() {
    method toggle (line 92) | toggle() {
    method refresh (line 105) | async refresh() {
    method updateMarkers (line 151) | updateMarkers(markers) {
    method getLayer (line 156) | getLayer() {
    method isVisible (line 161) | isVisible() {
    method remove (line 166) | remove() {
    method addToMap (line 173) | addToMap() {

FILE: app/javascript/maps/theme_utils.js
  function getThemeStyles (line 8) | function getThemeStyles(userTheme) {
  function applyThemeToControl (line 32) | function applyThemeToControl(element, userTheme, additionalStyles = {}) {
  function applyThemeToButton (line 50) | function applyThemeToButton(button, userTheme) {
  function applyThemeToPanel (line 74) | function applyThemeToPanel(panel, userTheme) {

FILE: app/javascript/maps/tracks.js
  function getTrackColor (line 17) | function getTrackColor() {
  function createTrackPopupContent (line 22) | function createTrackPopupContent(track, distanceUnit) {
  function addTrackInteractions (line 45) | function addTrackInteractions(
  function getTrackCoordinates (line 200) | function getTrackCoordinates(track) {
  function createTracksLayer (line 265) | function createTracksLayer(tracks, map, userSettings, distanceUnit) {
  function updateTracksColors (line 348) | function updateTracksColors(tracksLayer) {
  function updateTracksOpacity (line 363) | function updateTracksOpacity(tracksLayer, opacity) {
  function toggleTracksVisibility (line 373) | function toggleTracksVisibility(tracksLayer, map, isVisible) {
  function filterTracks (line 382) | function filterTracks(tracks, criteria) {
  function createSingleTrackLayer (line 413) | function createSingleTrackLayer(track, map, userSettings, distanceUnit) {
  function addOrUpdateTrack (line 462) | function addOrUpdateTrack(
  function removeTrackById (line 491) | function removeTrackById(tracksLayer, trackId) {
  function isTrackInTimeRange (line 522) | function isTrackInTimeRange(track, startAt, endAt) {
  function handleIncrementalTrackUpdate (line 544) | function handleIncrementalTrackUpdate(

FILE: app/javascript/maps/visits.js
  class VisitsManager (line 8) | class VisitsManager {
    method constructor (line 9) | constructor(map, apiKey, userTheme = "dark", mapsController = null) {
    method formatDuration (line 55) | formatDuration(seconds) {
    method toggleSelectionMode (line 81) | toggleSelectionMode() {
    method onMouseDown (line 111) | onMouseDown(e) {
    method onMouseMove (line 126) | onMouseMove(e) {
    method onMouseUp (line 145) | onMouseUp(_e) {
    method clearSelection (line 170) | clearSelection() {
    method fetchVisitsInSelection (line 197) | async fetchVisitsInSelection() {
    method filterPointsInSelection (line 252) | filterPointsInSelection(bounds) {
    method getPointsData (line 281) | getPointsData() {
    method groupVisitsByDate (line 302) | groupVisitsByDate(visits) {
    method createDateSummaryHtml (line 359) | createDateSummaryHtml(dateGroups) {
    method addSelectionCancelButton (line 406) | addSelectionCancelButton() {
    method deleteSelectedPoints (line 459) | async deleteSelectedPoints() {
    method updatePolylinesAfterDeletion (line 572) | updatePolylinesAfterDeletion() {
    method toggleDrawer (line 634) | toggleDrawer() {
    method createDrawer (line 674) | createDrawer() {
    method fetchAndDisplayVisits (line 716) | async fetchAndDisplayVisits() {
    method createMapCircles (line 810) | createMapCircles(visits) {
    method displayVisits (line 881) | displayVisits(visits) {
    method setupMergeFunctionality (line 1052) | setupMergeFunctionality(container) {
    method updateMergeUI (line 1096) | updateMergeUI(container) {
    method mergeVisits (line 1266) | async mergeVisits(visitIds) {
    method bulkUpdateVisitStatus (line 1303) | async bulkUpdateVisitStatus(visitIds, status) {
    method addVisitItemEventListeners (line 1343) | addVisitItemEventListeners(container) {
    method highlightVisit (line 1444) | highlightVisit(visitId, item, coords) {
    method clearVisitHighlight (line 1516) | clearVisitHighlight() {
    method fetchPossiblePlaces (line 1546) | async fetchPossiblePlaces(visit) {
    method addPopupFormEventListeners (line 1713) | addPopupFormEventListeners(visit) {
    method handleStatusChange (line 1824) | async handleStatusChange(event, visitId, status) {
    method handleDeleteVisit (line 1861) | async handleDeleteVisit(event, visitId) {
    method truncateText (line 1909) | truncateText(text, maxLength) {
    method getVisitCirclesLayer (line 1918) | getVisitCirclesLayer() {
    method getConfirmedVisitCirclesLayer (line 1926) | getConfirmedVisitCirclesLayer() {

FILE: app/javascript/maps_maplibre/channels/map_channel.js
  function createMapChannel (line 9) | function createMapChannel(options = {}) {

FILE: app/javascript/maps_maplibre/components/photo_popup.js
  class PhotoPopupFactory (line 6) | class PhotoPopupFactory {
    method createPhotoPopup (line 13) | static createPhotoPopup(properties, timezone = "UTC") {

FILE: app/javascript/maps_maplibre/components/toast.js
  class Toast (line 5) | class Toast {
    method init (line 11) | static init() {
    method addStyles (line 35) | static addStyles() {
    method show (line 81) | static show(message, type = "info", duration = 3000) {
    method dismiss (line 116) | static dismiss(toast) {
    method getBackgroundColor (line 130) | static getBackgroundColor(type) {
    method success (line 145) | static success(message, duration = 3000) {
    method error (line 154) | static error(message, duration = 4000) {
    method warning (line 163) | static warning(message, duration = 3500) {
    method info (line 172) | static info(message, duration = 3000) {
    method clearAll (line 179) | static clearAll() {

FILE: app/javascript/maps_maplibre/components/upgrade_banner.js
  class UpgradeBanner (line 5) | class UpgradeBanner {
    method show (line 18) | static show({ message, upgradeUrl, utmContent }) {
    method dismiss (line 74) | static dismiss() {

FILE: app/javascript/maps_maplibre/components/visit_card.js
  class VisitCard (line 4) | class VisitCard {
    method create (line 11) | static create(visit, options = {}) {
    method createBulkActions (line 131) | static createBulkActions(selectedCount) {

FILE: app/javascript/maps_maplibre/components/visit_popup.js
  class VisitPopupFactory (line 7) | class VisitPopupFactory {
    method createVisitPopup (line 13) | static createVisitPopup(properties) {

FILE: app/javascript/maps_maplibre/layers/areas_layer.js
  class AreasLayer (line 6) | class AreasLayer extends BaseLayer {
    method constructor (line 7) | constructor(map, options = {}) {
    method getSourceConfig (line 11) | getSourceConfig() {
    method getLayerConfigs (line 21) | getLayerConfigs() {
    method getLayerIds (line 64) | getLayerIds() {

FILE: app/javascript/maps_maplibre/layers/base_layer.js
  class BaseLayer (line 5) | class BaseLayer {
    method constructor (line 6) | constructor(map, options = {}) {
    method add (line 18) | add(data) {
    method update (line 52) | update(data) {
    method remove (line 63) | remove() {
    method show (line 80) | show() {
    method hide (line 88) | hide() {
    method toggle (line 97) | toggle(visible = !this.visible) {
    method setVisibility (line 106) | setVisibility(visible) {
    method getSourceConfig (line 119) | getSourceConfig() {
    method getLayerConfigs (line 127) | getLayerConfigs() {
    method getLayerIds (line 135) | getLayerIds() {

FILE: app/javascript/maps_maplibre/layers/family_layer.js
  class FamilyLayer (line 7) | class FamilyLayer extends BaseLayer {
    method constructor (line 8) | constructor(map, options = {}) {
    method getSourceConfig (line 14) | getSourceConfig() {
    method getLayerConfigs (line 24) | getLayerConfigs() {
    method getLayerIds (line 89) | getLayerIds() {
    method updateMember (line 102) | updateMember(member) {
    method appendToHistory (line 145) | appendToHistory(memberId, coords, color) {
    method getMemberColor (line 195) | getMemberColor(memberId) {
    method removeMember (line 214) | removeMember(memberId) {
    method loadMembers (line 228) | loadMembers(locations) {
    method loadMemberHistory (line 262) | loadMemberHistory(historyData) {
    method clearHistory (line 311) | clearHistory() {
    method centerOnMember (line 327) | centerOnMember(memberId) {
    method getMembers (line 344) | getMembers() {

FILE: app/javascript/maps_maplibre/layers/fog_layer.js
  class FogLayer (line 6) | class FogLayer {
    method constructor (line 7) | constructor(map, options = {}) {
    method add (line 18) | add(data) {
    method update (line 28) | update(data) {
    method createCanvas (line 34) | createCanvas() {
    method resizeCanvas (line 61) | resizeCanvas() {
    method render (line 70) | render() {
    method getMetersPerPixel (line 102) | getMetersPerPixel(latitude) {
    method show (line 109) | show() {
    method hide (line 117) | hide() {
    method toggle (line 124) | toggle(visible = !this.visible) {
    method remove (line 132) | remove() {

FILE: app/javascript/maps_maplibre/layers/heatmap_layer.js
  class HeatmapLayer (line 7) | class HeatmapLayer extends BaseLayer {
    method constructor (line 8) | constructor(map, options = {}) {
    method getSourceConfig (line 13) | getSourceConfig() {
    method getLayerConfigs (line 23) | getLayerConfigs() {

FILE: app/javascript/maps_maplibre/layers/photos_layer.js
  class PhotosLayer (line 11) | class PhotosLayer extends BaseLayer {
    method constructor (line 12) | constructor(map, options = {}) {
    method getSourceConfig (line 23) | getSourceConfig() {
    method getLayerConfigs (line 33) | getLayerConfigs() {
    method getLayerIds (line 72) | getLayerIds() {
    method add (line 76) | async add(data) {
    method update (line 108) | async update(data) {
    method _onMoveEnd (line 117) | _onMoveEnd() {
    method _onClusterClick (line 122) | _onClusterClick(e) {
    method _spiderfyCluster (line 156) | _spiderfyCluster(source, clusterId, center) {
    method _createSpiderLeg (line 184) | _createSpiderLeg(from, to) {
    method _clearSpiderfiedMarkers (line 212) | _clearSpiderfiedMarkers() {
    method _syncMarkers (line 231) | _syncMarkers() {
    method _computeSpiderOffsets (line 306) | _computeSpiderOffsets(visibleFeatures) {
    method _computeSpiralPositions (line 367) | _computeSpiralPositions(count, center) {
    method _updateLeg (line 415) | _updateLeg(featureId, origin, offset) {
    method _removeLeg (line 454) | _removeLeg(featureId) {
    method _clearAllLegs (line 466) | _clearAllLegs() {
    method _createPhotoMarker (line 477) | _createPhotoMarker(feature, lngLat) {
    method showPhotoPopup (line 529) | showPhotoPopup(feature) {
    method clearMarkers (line 588) | clearMarkers() {
    method show (line 599) | show() {
    method hide (line 617) | hide() {
    method remove (line 634) | remove() {

FILE: app/javascript/maps_maplibre/layers/places_layer.js
  class PlacesLayer (line 7) | class PlacesLayer extends BaseLayer {
    method constructor (line 8) | constructor(map, options = {}) {
    method getSourceConfig (line 12) | getSourceConfig() {
    method getLayerConfigs (line 22) | getLayerConfigs() {
    method getLayerIds (line 63) | getLayerIds() {

FILE: app/javascript/maps_maplibre/layers/points_layer.js
  class PointsLayer (line 8) | class PointsLayer extends BaseLayer {
    method constructor (line 9) | constructor(map, options = {}) {
    method getSourceConfig (line 25) | getSourceConfig() {
    method getLayerConfigs (line 35) | getLayerConfigs() {
    method enableDragging (line 55) | enableDragging() {
    method disableDragging (line 72) | disableDragging() {
    method onMouseEnter (line 82) | onMouseEnter() {
    method onMouseLeave (line 86) | onMouseLeave() {
    method onMouseDown (line 92) | onMouseDown(e) {
    method onMouseMove (line 106) | onMouseMove(e) {
    method onMouseUp (line 126) | async onMouseUp(e) {
    method updatePointPosition (line 168) | async updatePointPosition(pointId, latitude, longitude) {
    method updateConnectedRoutes (line 198) | async updateConnectedRoutes(_pointId, oldCoords, newCoords) {
    method add (line 256) | add(data) {
    method remove (line 268) | remove() {

FILE: app/javascript/maps_maplibre/layers/recent_point_layer.js
  class RecentPointLayer (line 7) | class RecentPointLayer extends BaseLayer {
    method constructor (line 8) | constructor(map, options = {}) {
    method getSourceConfig (line 12) | getSourceConfig() {
    method getLayerConfigs (line 22) | getLayerConfigs() {
    method updateRecentPoint (line 56) | updateRecentPoint(lon, lat, properties = {}) {
    method clear (line 76) | clear() {

FILE: app/javascript/maps_maplibre/layers/replay_marker_layer.js
  class ReplayMarkerLayer (line 10) | class ReplayMarkerLayer extends BaseLayer {
    method constructor (line 11) | constructor(map, options = {}) {
    method getSourceConfig (line 17) | getSourceConfig() {
    method getLayerConfigs (line 27) | getLayerConfigs() {
    method showMarker (line 61) | showMarker(lon, lat, properties = {}) {
    method getCurrentEmoji (line 108) | getCurrentEmoji() {
    method hideMarker (line 115) | hideMarker() {
    method clear (line 123) | clear() {
    method _showEmojiMarker (line 137) | _showEmojiMarker(lon, lat, emoji) {
    method _removeEmojiMarker (line 159) | _removeEmojiMarker() {
    method _hideCircleLayer (line 170) | _hideCircleLayer() {
    method _showCircleLayer (line 184) | _showCircleLayer() {

FILE: app/javascript/maps_maplibre/layers/routes_layer.js
  class RoutesLayer (line 9) | class RoutesLayer extends BaseLayer {
    method constructor (line 10) | constructor(map, options = {}) {
    method getSourceConfig (line 19) | getSourceConfig() {
    method add (line 32) | add(data) {
    method getLayerConfigs (line 68) | getLayerConfigs() {
    method update (line 135) | update(data) {
    method updateBaseData (line 157) | updateBaseData(data) {
    method setVisibility (line 169) | setVisibility(visible) {
    method setHoverRoute (line 184) | setHoverRoute(feature) {
    method remove (line 206) | remove() {
    method haversineDistance (line 244) | static haversineDistance(lat1, lon1, lat2, lon2) {
    method pointsToRoutes (line 255) | static pointsToRoutes(points, options = {}) {

FILE: app/javascript/maps_maplibre/layers/scratch_layer.js
  class ScratchLayer (line 10) | class ScratchLayer extends BaseLayer {
    method constructor (line 11) | constructor(map, options = {}) {
    method add (line 19) | async add(data) {
    method update (line 34) | async update(data) {
    method detectCountriesFromPoints (line 51) | detectCountriesFromPoints(points) {
    method loadCountryBoundaries (line 70) | async loadCountryBoundaries() {
    method createCountriesGeoJSON (line 118) | createCountriesGeoJSON() {
    method getSourceConfig (line 145) | getSourceConfig() {
    method getLayerConfigs (line 155) | getLayerConfigs() {
    method getLayerIds (line 181) | getLayerIds() {

FILE: app/javascript/maps_maplibre/layers/selected_points_layer.js
  class SelectedPointsLayer (line 6) | class SelectedPointsLayer extends BaseLayer {
    method constructor (line 7) | constructor(map, options = {}) {
    method getSourceConfig (line 12) | getSourceConfig() {
    method getLayerConfigs (line 22) | getLayerConfigs() {
    method getLayerIds (line 53) | getLayerIds() {
    method updateSelectedPoints (line 60) | updateSelectedPoints(geojson) {
    method getSelectedPointIds (line 79) | getSelectedPointIds() {
    method clearSelection (line 86) | clearSelection() {
    method getCount (line 97) | getCount() {

FILE: app/javascript/maps_maplibre/layers/selection_layer.js
  class SelectionLayer (line 7) | class SelectionLayer extends BaseLayer {
    method constructor (line 8) | constructor(map, options = {}) {
    method getSourceConfig (line 16) | getSourceConfig() {
    method getLayerConfigs (line 26) | getLayerConfigs() {
    method getLayerIds (line 55) | getLayerIds() {
    method enableSelectionMode (line 62) | enableSelectionMode() {
    method disableSelectionMode (line 80) | disableSelectionMode() {
    method onMouseDown (line 99) | onMouseDown(e) {
    method onMouseMove (line 112) | onMouseMove(e) {
    method onMouseUp (line 140) | onMouseUp(e) {
    method createRectangle (line 160) | createRectangle(start, end) {
    method calculateBounds (line 173) | calculateBounds(start, end) {
    method clearSelection (line 185) | clearSelection() {
    method remove (line 198) | remove() {

FILE: app/javascript/maps_maplibre/layers/track_points_layer.js
  class TrackPointsLayer (line 8) | class TrackPointsLayer extends BaseLayer {
    method constructor (line 9) | constructor(map, options = {}) {
    method getSourceConfig (line 27) | getSourceConfig() {
    method getLayerConfigs (line 37) | getLayerConfigs() {
    method loadTrackPoints (line 58) | async loadTrackPoints(trackId) {
    method pointsToGeoJSON (line 95) | pointsToGeoJSON(points) {
    method enableDragging (line 124) | enableDragging() {
    method disableDragging (line 138) | disableDragging() {
    method onMouseEnter (line 148) | onMouseEnter() {
    method onMouseLeave (line 152) | onMouseLeave() {
    method onMouseDown (line 158) | onMouseDown(e) {
    method onMouseMove (line 170) | onMouseMove(e) {
    method onMouseUp (line 194) | async onMouseUp(e) {
    method updatePointPosition (line 249) | async updatePointPosition(pointId, latitude, longitude) {
    method clear (line 279) | clear() {
    method remove (line 288) | remove() {

FILE: app/javascript/maps_maplibre/layers/tracks_layer.js
  class TracksLayer (line 9) | class TracksLayer extends BaseLayer {
    method constructor (line 10) | constructor(map, options = {}) {
    method getSourceConfig (line 31) | getSourceConfig() {
    method getLayerConfigs (line 41) | getLayerConfigs() {
    method add (line 93) | add(data) {
    method setSelectedTrack (line 125) | setSelectedTrack(feature) {
    method _buildFlowGradient (line 170) | _buildFlowGradient(
    method _startFlowAnimation (line 249) | _startFlowAnimation() {
    method _stopFlowAnimation (line 303) | _stopFlowAnimation() {
    method _computeLineLength (line 316) | _computeLineLength(coordinates) {
    method showSegments (line 337) | showSegments(trackFeature, segments) {
    method hideSegments (line 436) | hideSegments() {
    method _setupSegmentHoverEvents (line 448) | _setupSegmentHoverEvents() {
    method _removeSegmentHoverEvents (line 482) | _removeSegmentHoverEvents() {
    method setSegmentHoverCallback (line 505) | setSegmentHoverCallback(callback) {
    method setSegmentLeaveCallback (line 513) | setSegmentLeaveCallback(callback) {
    method updateTrackFeature (line 525) | updateTrackFeature(trackFeature, options = {}) {
    method remove (line 582) | remove() {

FILE: app/javascript/maps_maplibre/layers/visits_layer.js
  class VisitsLayer (line 7) | class VisitsLayer extends BaseLayer {
    method constructor (line 8) | constructor(map, options = {}) {
    method getSourceConfig (line 12) | getSourceConfig() {
    method getLayerConfigs (line 22) | getLayerConfigs() {
    method getLayerIds (line 64) | getLayerIds() {

FILE: app/javascript/maps_maplibre/managers/replay_manager.js
  class ReplayManager (line 5) | class ReplayManager {
    method constructor (line 6) | constructor(options = {}) {
    method setPoints (line 23) | setPoints(points) {
    method _parseTimestamp (line 36) | _parseTimestamp(timestamp) {
    method groupPointsByDay (line 60) | groupPointsByDay() {
    method buildMinuteIndex (line 94) | buildMinuteIndex() {
    method getDataRanges (line 126) | getDataRanges() {
    method getDataDensity (line 154) | getDataDensity(segments = 48) {
    method hasDataAtMinute (line 175) | hasDataAtMinute(minute) {
    method getCurrentDay (line 183) | getCurrentDay() {
    method getCurrentDayDisplay (line 192) | getCurrentDayDisplay() {
    method getPointsAtMinute (line 211) | getPointsAtMinute(minute) {
    method findNearestMinuteWithPoints (line 220) | findNearestMinuteWithPoints(minute) {
    method getPointAtPosition (line 250) | getPointAtPosition(minute) {
    method getPointCountAtMinute (line 263) | getPointCountAtMinute(minute) {
    method pinPoint (line 271) | pinPoint(point) {
    method unpinPoint (line 279) | unpinPoint() {
    method isPinned (line 289) | isPinned() {
    method prevDay (line 297) | prevDay() {
    method nextDay (line 312) | nextDay() {
    method canGoPrev (line 327) | canGoPrev() {
    method canGoNext (line 335) | canGoNext() {
    method cyclePrev (line 342) | cyclePrev() {
    method cycleNext (line 350) | cycleNext(minute) {
    method resetCycle (line 360) | resetCycle() {
    method getDayCount (line 368) | getDayCount() {
    method hasData (line 376) | hasData() {
    method getCurrentDayPointCount (line 384) | getCurrentDayPointCount() {
    method formatMinuteToTime (line 395) | static formatMinuteToTime(minute) {
    method findTransportationEmoji (line 407) | static findTransportationEmoji(point, tracksGeoJSON) {
    method _getTimestampStatic (line 462) | static _getTimestampStatic(point) {
    method _parseTimestampStatic (line 479) | static _parseTimestampStatic(timestamp) {
    method _getTimestamp (line 507) | _getTimestamp(point) {
    method _formatDayKey (line 523) | _formatDayKey(date) {
    method getCoordinates (line 535) | getCoordinates(point) {

FILE: app/javascript/maps_maplibre/services/api_client.js
  class ApiClient (line 5) | class ApiClient {
    method constructor (line 6) | constructor(apiKey) {
    method fetchPoints (line 16) | async fetchPoints({ start_at, end_at, page = 1, per_page = 1000 }) {
    method fetchAllPoints (line 57) | async fetchAllPoints({
    method fetchVisitsPage (line 171) | async fetchVisitsPage({ start_at, end_at, page = 1, per_page = 500 }) {
    method fetchVisits (line 201) | async fetchVisits({ start_at, end_at, onProgress = null }) {
    method fetchPlacesPage (line 236) | async fetchPlacesPage({ tag_ids = [], page = 1, per_page = 500 } = {}) {
    method fetchPlaces (line 272) | async fetchPlaces({ tag_ids = [], onProgress = null } = {}) {
    method fetchPhotos (line 305) | async fetchPhotos({ start_at, end_at }) {
    method fetchAreas (line 329) | async fetchAreas() {
    method fetchArea (line 345) | async fetchArea(areaId) {
    method fetchTracksPage (line 362) | async fetchTracksPage({ start_at, end_at, page = 1, per_page = 500 }) {
    method fetchTracks (line 396) | async fetchTracks({
    method fetchTrackWithSegments (line 483) | async fetchTrackWithSegments(trackId) {
    method createArea (line 504) | async createArea(area) {
    method deleteArea (line 522) | async deleteArea(areaId) {
    method fetchPointsInArea (line 540) | async fetchPointsInArea({
    method fetchVisitsInArea (line 574) | async fetchVisitsInArea({
    method bulkDeletePoints (line 608) | async bulkDeletePoints(pointIds) {
    method updateVisitStatus (line 628) | async updateVisitStatus(visitId, status) {
    method mergeVisits (line 647) | async mergeVisits(visitIds) {
    method bulkUpdateVisits (line 667) | async bulkUpdateVisits(visitIds, status) {
    method fetchTrackPointsPage (line 687) | async fetchTrackPointsPage(trackId, { page = 1, per_page = 1000 } = {}) {
    method fetchTrackPoints (line 717) | async fetchTrackPoints(trackId, { per_page = 1000, maxConcurrent = 3 }...
    method fetchTimeline (line 756) | async fetchTimeline({ start_at, end_at }) {
    method getHeaders (line 770) | getHeaders() {

FILE: app/javascript/maps_maplibre/services/location_search_service.js
  class LocationSearchService (line 6) | class LocationSearchService {
    method constructor (line 7) | constructor(apiKey) {
    method fetchSuggestions (line 20) | async fetchSuggestions(query) {
    method searchVisits (line 66) | async searchVisits({ lat, lon, name, address = "" }) {
    method createVisit (line 97) | async createVisit(visitData) {

FILE: app/javascript/maps_maplibre/utils/cleanup_helper.js
  class CleanupHelper (line 5) | class CleanupHelper {
    method constructor (line 6) | constructor() {
    method addEventListener (line 13) | addEventListener(target, event, handler, options) {
    method setInterval (line 18) | setInterval(callback, delay) {
    method setTimeout (line 24) | setTimeout(callback, delay) {
    method addObserver (line 30) | addObserver(observer) {
    method cleanup (line 34) | cleanup() {

FILE: app/javascript/maps_maplibre/utils/fps_monitor.js
  class FPSMonitor (line 5) | class FPSMonitor {
    method constructor (line 6) | constructor(sampleSize = 60) {
    method start (line 14) | start() {
    method stop (line 20) | stop() {
    method getFPS (line 28) | getFPS() {

FILE: app/javascript/maps_maplibre/utils/geojson_transformers.js
  function pointsToGeoJSON (line 6) | function pointsToGeoJSON(points) {
  function formatTimestamp (line 34) | function formatTimestamp(timestamp, timezone = "UTC") {
  function formatTimeOnly (line 66) | function formatTimeOnly(timestamp, timezone = "UTC") {
  function escapeHtml (line 93) | function escapeHtml(value) {

FILE: app/javascript/maps_maplibre/utils/geometry.js
  function calculateDistance (line 7) | function calculateDistance(point1, point2) {
  function createCircle (line 33) | function createCircle(center, radiusInMeters, points = 64) {
  function createRectangle (line 57) | function createRectangle(bounds) {

FILE: app/javascript/maps_maplibre/utils/layer_gate.js
  constant PREVIEW_SECONDS (line 12) | const PREVIEW_SECONDS = 20
  function isGatedPlan (line 21) | function isGatedPlan(userPlan) {
  function gatedToggle (line 36) | function gatedToggle({
  function startPreview (line 60) | async function startPreview({ layerName, toggle, showFn, hideFn, upgrade...
  function cancelPreview (line 105) | function cancelPreview(layerName) {
  function cancelAllPreviews (line 121) | function cancelAllPreviews() {

FILE: app/javascript/maps_maplibre/utils/lazy_loader.js
  class LazyLoader (line 5) | class LazyLoader {
    method constructor (line 6) | constructor() {
    method loadLayer (line 16) | async loadLayer(name) {
    method #load (line 42) | async #load(name) {
    method #getClassName (line 57) | #getClassName(name) {
    method preload (line 66) | async preload(names) {
    method clear (line 70) | clear() {

FILE: app/javascript/maps_maplibre/utils/performance_monitor.js
  class PerformanceMonitor (line 5) | class PerformanceMonitor {
    method constructor (line 6) | constructor() {
    method mark (line 15) | mark(name) {
    method measure (line 24) | measure(name) {
    method getReport (line 47) | getReport() {
    method getMemoryUsage (line 77) | getMemoryUsage() {
    method logReport (line 90) | logReport() {
    method clear (line 104) | clear() {

FILE: app/javascript/maps_maplibre/utils/popup_theme.js
  function getCurrentTheme (line 10) | function getCurrentTheme() {
  function getThemeColors (line 25) | function getThemeColors(theme = getCurrentTheme()) {
  function getPopupBaseStyles (line 80) | function getPopupBaseStyles(theme = getCurrentTheme()) {
  function getPopupClass (line 96) | function getPopupClass(baseClass, theme = getCurrentTheme()) {
  function onThemeChange (line 105) | function onThemeChange(callback) {

FILE: app/javascript/maps_maplibre/utils/progressive_loader.js
  class ProgressiveLoader (line 5) | class ProgressiveLoader {
    method constructor (line 6) | constructor(options = {}) {
    method load (line 18) | async load(fetchFn, options = {}) {
    method cancel (line 96) | cancel() {

FILE: app/javascript/maps_maplibre/utils/route_segmenter.js
  class RouteSegmenter (line 5) | class RouteSegmenter {
    method haversineDistance (line 14) | static haversineDistance(lat1, lon1, lat2, lon2) {
    method getInterpolatedLat (line 38) | static getInterpolatedLat(lat1, lon1, lat2, lon2, interpLon) {
    method findIDLCrossings (line 79) | static findIDLCrossings(coords) {
    method unwrapCoordinates (line 104) | static unwrapCoordinates(segment) {
    method calculateSegmentDistance (line 152) | static calculateSegmentDistance(segment) {
    method splitIntoSegments (line 173) | static splitIntoSegments(points, options) {
    method segmentToFeature (line 217) | static segmentToFeature(segment) {
    method pointsToRoutes (line 256) | static pointsToRoutes(points, options = {}) {

FILE: app/javascript/maps_maplibre/utils/search_manager.js
  class SearchManager (line 8) | class SearchManager {
    method constructor (line 9) | constructor(map, apiKey) {
    method initialize (line 25) | initialize(searchInput, resultsContainer) {
    method attachEventListeners (line 40) | attachEventListeners() {
    method handleSearchInput (line 82) | handleSearchInput(query) {
    method displayResults (line 106) | displayResults(suggestions) {
    method createResultItem (line 127) | createResultItem(suggestion) {
    method handleResultClick (line 159) | async handleResultClick(location) {
    method addSearchMarker (line 203) | addSearchMarker(lon, lat) {
    method dispatchSearchEvent (line 238) | dispatchSearchEvent(location) {
    method showLoading (line 249) | showLoading() {
    method showNoResults (line 263) | showNoResults() {
    method showError (line 276) | showError(message) {
    method showVisitsLoading (line 289) | showVisitsLoading(locationName) {
    method displayVisitsResults (line 307) | displayVisitsResults(visitsData, location) {
    method buildLocationVisitsHtml (line 348) | buildLocationVisitsHtml(location, index) {
    method groupVisitsByYear (line 426) | groupVisitsByYear(visits) {
    method attachYearToggleListeners (line 441) | attachYearToggleListeners() {
    method handleVisitClick (line 478) | handleVisitClick(locationIndex, visitIndex) {
    method openCreateVisitModal (line 516) | openCreateVisitModal(visitData) {
    method submitCreateVisit (line 604) | async submitCreateVisit(form, modal) {
    method showSuccessNotification (line 663) | showSuccessNotification(message) {
    method formatDateTimeForInput (line 683) | formatDateTimeForInput(dateString) {
    method formatDateShort (line 698) | formatDateShort(dateString) {
    method formatDateTime (line 712) | formatDateTime(dateString) {
    method escapeHtml (line 728) | escapeHtml(str) {
    method clearResults (line 738) | clearResults() {
    method clearMarker (line 748) | clearMarker() {
    method destroy (line 758) | destroy() {

FILE: app/javascript/maps_maplibre/utils/settings_manager.js
  constant DEFAULT_SETTINGS (line 6) | const DEFAULT_SETTINGS = {
  constant LAYER_NAME_MAP (line 38) | const LAYER_NAME_MAP = {
  constant BACKEND_SETTINGS_MAP (line 52) | const BACKEND_SETTINGS_MAP = {
  constant TRANSPORTATION_THRESHOLD_MAP (line 73) | const TRANSPORTATION_THRESHOLD_MAP = {
  constant TRANSPORTATION_EXPERT_THRESHOLD_MAP (line 80) | const TRANSPORTATION_EXPERT_THRESHOLD_MAP = {
  class SettingsManager (line 90) | class SettingsManager {
    method initialize (line 98) | static initialize(apiKey) {
    method getSettings (line 108) | static getSettings() {
    method _expandLayerSettings (line 125) | static _expandLayerSettings(settings) {
    method _collapseLayerSettings (line 140) | static _collapseLayerSettings(settings) {
    method _convertTransportationThresholds (line 159) | static _convertTransportationThresholds(
    method _parseIntOr (line 183) | static _parseIntOr(value, fallback) {
    method _parseFloatOr (line 188) | static _parseFloatOr(value, fallback) {
    method loadFromBackend (line 197) | static async loadFromBackend() {
    method updateCache (line 311) | static updateCache(settings) {
    method saveToBackend (line 320) | static async saveToBackend(settings) {
    method getSetting (line 405) | static getSetting(key) {
    method updateSetting (line 415) | static async updateSetting(key, value) {
    method resetToDefaults (line 433) | static async resetToDefaults() {
    method sync (line 450) | static async sync() {

FILE: app/javascript/maps_maplibre/utils/speed_colors.js
  function colorFormatEncode (line 20) | function colorFormatEncode(arr) {
  function colorFormatDecode (line 29) | function colorFormatDecode(str) {
  function hexToRGB (line 41) | function hexToRGB(hex) {
  function calculateSpeed (line 54) | function calculateSpeed(point1, point2) {
  function haversineDistance (line 87) | function haversineDistance(lat1, lon1, lat2, lon2) {
  function getSpeedColor (line 108) | function getSpeedColor(speedKmh, useSpeedColors, speedColorScale) {
  function applySpeedColors (line 160) | function applySpeedColors(routesGeoJSON, points, speedColorScale) {

FILE: app/javascript/maps_maplibre/utils/style_manager.js
  constant TILE_SOURCE_URL (line 6) | const TILE_SOURCE_URL = "https://tyles.dwri.xyz/planet/{z}/{x}/{y}.mvt"
  constant MAP_STYLES (line 14) | const MAP_STYLES = {
  function loadStyleFile (line 27) | async function loadStyleFile(styleName) {
  function getMapStyle (line 49) | async function getMapStyle(styleName = "light") {
  function getAvailableStyles (line 86) | function getAvailableStyles() {
  function getStyleDisplayName (line 95) | function getStyleDisplayName(styleName) {
  function preloadAllStyles (line 113) | async function preloadAllStyles() {

FILE: app/javascript/maps_maplibre/utils/websocket_manager.js
  class WebSocketManager (line 5) | class WebSocketManager {
    method constructor (line 6) | constructor(options = {}) {
    method connect (line 21) | connect(subscription) {
    method attemptReconnect (line 41) | attemptReconnect() {
    method disconnect (line 65) | disconnect() {
    method send (line 76) | send(action, data = {}) {

FILE: app/javascript/posthog.js
  function g (line 8) | function g(t, e) {

FILE: app/jobs/app_version_checking_job.rb
  class AppVersionCheckingJob (line 3) | class AppVersionCheckingJob < ApplicationJob
    method perform (line 7) | def perform

FILE: app/jobs/application_job.rb
  class ApplicationJob (line 3) | class ApplicationJob < ActiveJob::Base
    method find_user_or_skip (line 15) | def find_user_or_skip(user_id)

FILE: app/jobs/area_visits_calculating_job.rb
  class AreaVisitsCalculatingJob (line 3) | class AreaVisitsCalculatingJob < ApplicationJob
    method perform (line 9) | def perform(user_id)

FILE: app/jobs/area_visits_calculation_scheduling_job.rb
  class AreaVisitsCalculationSchedulingJob (line 3) | class AreaVisitsCalculationSchedulingJob < ApplicationJob
    method perform (line 7) | def perform

FILE: app/jobs/bulk_stats_calculating_job.rb
  class BulkStatsCalculatingJob (line 3) | class BulkStatsCalculatingJob < ApplicationJob
    method perform (line 6) | def perform

FILE: app/jobs/bulk_visits_suggesting_job.rb
  class BulkVisitsSuggestingJob (line 5) | class BulkVisitsSuggestingJob < ApplicationJob
    method perform (line 10) | def perform(start_at: 1.day.ago.beginning_of_day, end_at: 1.day.ago.en...
    method schedule_chunked_jobs (line 29) | def schedule_chunked_jobs(user, time_chunks)

FILE: app/jobs/cache/cleaning_job.rb
  class Cache::CleaningJob (line 3) | class Cache::CleaningJob < ApplicationJob
    method perform (line 6) | def perform

FILE: app/jobs/cache/preheating_job.rb
  class Cache::PreheatingJob (line 3) | class Cache::PreheatingJob < ApplicationJob
    method perform (line 6) | def perform

FILE: app/jobs/concerns/user_timezone.rb
  type UserTimezone (line 3) | module UserTimezone
    function with_user_timezone (line 8) | def with_user_timezone(user, &block)

FILE: app/jobs/data_migrations/backfill_country_name_job.rb
  class DataMigrations::BackfillCountryNameJob (line 3) | class DataMigrations::BackfillCountryNameJob < ApplicationJob
    method perform (line 6) | def perform(batch_size: 1000)
    method country_name (line 28) | def country_name(point)

FILE: app/jobs/data_migrations/backfill_motion_data_job.rb
  class DataMigrations::BackfillMotionDataJob (line 3) | class DataMigrations::BackfillMotionDataJob < ApplicationJob
    method perform (line 8) | def perform(batch_size: BATCH_SIZE)
    method build_update (line 30) | def build_update(point)

FILE: app/jobs/data_migrations/backfill_onboarding_completed_job.rb
  class DataMigrations::BackfillOnboardingCompletedJob (line 3) | class DataMigrations::BackfillOnboardingCompletedJob < ApplicationJob
    method perform (line 6) | def perform

FILE: app/jobs/data_migrations/fix_route_opacity_job.rb
  class DataMigrations::FixRouteOpacityJob (line 3) | class DataMigrations::FixRouteOpacityJob < ApplicationJob
    method perform (line 6) | def perform

FILE: app/jobs/data_migrations/migrate_places_lonlat_job.rb
  class DataMigrations::MigratePlacesLonlatJob (line 3) | class DataMigrations::MigratePlacesLonlatJob < ApplicationJob
    method perform (line 6) | def perform(user_id)

FILE: app/jobs/data_migrations/migrate_points_latlon_job.rb
  class DataMigrations::MigratePointsLatlonJob (line 3) | class DataMigrations::MigratePointsLatlonJob < ApplicationJob
    method perform (line 6) | def perform(user_id)

FILE: app/jobs/data_migrations/prefill_points_counter_cache_job.rb
  class DataMigrations::PrefillPointsCounterCacheJob (line 3) | class DataMigrations::PrefillPointsCounterCacheJob < ApplicationJob
    method perform (line 6) | def perform(user_id = nil)
    method prefill_counter_for_user (line 18) | def prefill_counter_for_user(user_id)

FILE: app/jobs/data_migrations/set_points_country_ids_job.rb
  class DataMigrations::SetPointsCountryIdsJob (line 3) | class DataMigrations::SetPointsCountryIdsJob < ApplicationJob
    method perform (line 6) | def perform(point_id)

FILE: app/jobs/data_migrations/set_reverse_geocoded_at_for_points_job.rb
  class DataMigrations::SetReverseGeocodedAtForPointsJob (line 3) | class DataMigrations::SetReverseGeocodedAtForPointsJob < ApplicationJob
    method perform (line 6) | def perform

FILE: app/jobs/data_migrations/start_settings_points_country_ids_job.rb
  class DataMigrations::StartSettingsPointsCountryIdsJob (line 3) | class DataMigrations::StartSettingsPointsCountryIdsJob < ApplicationJob
    method perform (line 6) | def perform

FILE: app/jobs/enqueue_background_job.rb
  class EnqueueBackgroundJob (line 3) | class EnqueueBackgroundJob < ApplicationJob
    method perform (line 6) | def perform(job_name, user_id)

FILE: app/jobs/export_job.rb
  class ExportJob (line 3) | class ExportJob < ApplicationJob
    method perform (line 7) | def perform(export_id)

FILE: app/jobs/families/expire_location_requests_job.rb
  class Families::ExpireLocationRequestsJob (line 3) | class Families::ExpireLocationRequestsJob < ApplicationJob
    method perform (line 6) | def perform

FILE: app/jobs/family/invitations/cleanup_job.rb
  class Family::Invitations::CleanupJob (line 3) | class Family::Invitations::CleanupJob < ApplicationJob
    method perform (line 6) | def perform

FILE: app/jobs/family/invitations/sending_job.rb
  class Family::Invitations::SendingJob (line 3) | class Family::Invitations::SendingJob < ApplicationJob
    method perform (line 6) | def perform(invitation_id)

FILE: app/jobs/import/google_takeout_job.rb
  class Import::GoogleTakeoutJob (line 3) | class Import::GoogleTakeoutJob < ApplicationJob
    method perform (line 7) | def perform(import_id, locations, current_index)

FILE: app/jobs/import/immich_geodata_job.rb
  class Import::ImmichGeodataJob (line 3) | class Import::ImmichGeodataJob < ApplicationJob
    method perform (line 6) | def perform(user_id)

FILE: app/jobs/import/photoprism_geodata_job.rb
  class Import::PhotoprismGeodataJob (line 3) | class Import::PhotoprismGeodataJob < ApplicationJob
    method perform (line 7) | def perform(user_id)

FILE: app/jobs/import/process_job.rb
  class Import::ProcessJob (line 3) | class Import::ProcessJob < ApplicationJob
    method perform (line 6) | def perform(import_id)

FILE: app/jobs/import/update_points_count_job.rb
  class Import::UpdatePointsCountJob (line 3) | class Import::UpdatePointsCountJob < ApplicationJob
    method perform (line 6) | def perform(import_id)

FILE: app/jobs/import/watcher_job.rb
  class Import::WatcherJob (line 3) | class Import::WatcherJob < ApplicationJob
    method perform (line 7) | def perform

FILE: app/jobs/imports/destroy_job.rb
  class Imports::DestroyJob (line 3) | class Imports::DestroyJob < ApplicationJob
    method perform (line 6) | def perform(import_id)
    method broadcast_status_update (line 22) | def broadcast_status_update(import)
    method broadcast_deletion_complete (line 35) | def broadcast_deletion_complete(import)

FILE: app/jobs/lite/archival_warning_job.rb
  class Lite::ArchivalWarningJob (line 3) | class Lite::ArchivalWarningJob < ApplicationJob
    method perform (line 14) | def perform
    method check_thresholds (line 24) | def check_thresholds(user)
    method notify_approaching (line 39) | def notify_approaching(user)
    method notify_email (line 49) | def notify_email(user)
    method notify_archived (line 53) | def notify_archived(user)
    method mark_warning_sent (line 64) | def mark_warning_sent(user, key)

FILE: app/jobs/place_visits_calculating_job.rb
  class PlaceVisitsCalculatingJob (line 3) | class PlaceVisitsCalculatingJob < ApplicationJob
    method perform (line 7) | def perform(user_id)

FILE: app/jobs/places/bulk_name_fetching_job.rb
  class Places::BulkNameFetchingJob (line 3) | class Places::BulkNameFetchingJob < ApplicationJob
    method perform (line 6) | def perform

FILE: app/jobs/places/name_fetching_job.rb
  class Places::NameFetchingJob (line 3) | class Places::NameFetchingJob < ApplicationJob
    method perform (line 6) | def perform(place_id)

FILE: app/jobs/points/create_job.rb
  class Points::CreateJob (line 3) | class Points::CreateJob < ApplicationJob
    method perform (line 6) | def perform(params, user_id)

FILE: app/jobs/points/nightly_reverse_geocoding_job.rb
  class Points::NightlyReverseGeocodingJob (line 3) | class Points::NightlyReverseGeocodingJob < ApplicationJob
    method perform (line 6) | def perform

FILE: app/jobs/points/raw_data/archive_job.rb
  type Points (line 3) | module Points
    type RawData (line 4) | module RawData
      class ArchiveJob (line 5) | class ArchiveJob < ApplicationJob
        method perform (line 8) | def perform

FILE: app/jobs/points/raw_data/re_archive_month_job.rb
  type Points (line 3) | module Points
    type RawData (line 4) | module RawData
      class ReArchiveMonthJob (line 5) | class ReArchiveMonthJob < ApplicationJob
        method perform (line 8) | def perform(user_id, year, month)

FILE: app/jobs/reverse_geocoding_job.rb
  class ReverseGeocodingJob (line 3) | class ReverseGeocodingJob < ApplicationJob
    method perform (line 6) | def perform(klass, id)
    method data_fetcher (line 16) | def data_fetcher(klass, id)
    method rate_limit_for_photon_api (line 20) | def rate_limit_for_photon_api

FILE: app/jobs/stale_jobs_recovery_job.rb
  class StaleJobsRecoveryJob (line 3) | class StaleJobsRecoveryJob < ApplicationJob
    method perform (line 10) | def perform
    method recover_stale_exports (line 17) | def recover_stale_exports
    method recover_stale_imports (line 32) | def recover_stale_imports

FILE: app/jobs/stats/calculating_job.rb
  class Stats::CalculatingJob (line 3) | class Stats::CalculatingJob < ApplicationJob
    method perform (line 6) | def perform(user_id, year, month)
    method create_stats_update_failed_notification (line 14) | def create_stats_update_failed_notification(user_id, error)

FILE: app/jobs/tracks/boundary_resolver_job.rb
  class Tracks::BoundaryResolverJob (line 5) | class Tracks::BoundaryResolverJob < ApplicationJob
    method perform (line 10) | def perform(user_id, session_id, retry_count = 0)
    method session_exists_and_ready? (line 30) | def session_exists_and_ready?
    method resolve_boundary_tracks (line 42) | def resolve_boundary_tracks
    method finalize_session (line 47) | def finalize_session(_boundary_tracks_resolved)
    method reschedule_boundary_resolution (line 54) | def reschedule_boundary_resolution
    method mark_session_failed (line 66) | def mark_session_failed(error_message)

FILE: app/jobs/tracks/daily_generation_job.rb
  class Tracks::DailyGenerationJob (line 19) | class Tracks::DailyGenerationJob < ApplicationJob
    method perform (line 24) | def perform
    method process_user_daily_tracks (line 36) | def process_user_daily_tracks(user)
    method start_timestamp (line 51) | def start_timestamp(user)

FILE: app/jobs/tracks/deduplication_job.rb
  class Tracks::DeduplicationJob (line 5) | class Tracks::DeduplicationJob < ApplicationJob
    method perform (line 8) | def perform(user_id)

FILE: app/jobs/tracks/parallel_generator_job.rb
  class Tracks::ParallelGeneratorJob (line 5) | class Tracks::ParallelGeneratorJob < ApplicationJob
    method perform (line 8) | def perform(user_id, start_at: nil, end_at: nil, mode: :bulk, chunk_si...

FILE: app/jobs/tracks/realtime_generation_job.rb
  class Tracks::RealtimeGenerationJob (line 17) | class Tracks::RealtimeGenerationJob < ApplicationJob
    method perform (line 20) | def perform(user_id)

FILE: app/jobs/tracks/recalculate_job.rb
  class Tracks::RecalculateJob (line 3) | class Tracks::RecalculateJob < ApplicationJob
    method perform (line 6) | def perform(track_id)

FILE: app/jobs/tracks/time_chunk_processor_job.rb
  class Tracks::TimeChunkProcessorJob (line 5) | class Tracks::TimeChunkProcessorJob < ApplicationJob
    method perform (line 11) | def perform(user_id, session_id, chunk_data)
    method session_exists? (line 31) | def session_exists?
    method process_chunk (line 39) | def process_chunk
    method load_chunk_points (line 57) | def load_chunk_points
    method segment_chunk_points (line 63) | def segment_chunk_points(points)
    method segment_overlaps_chunk_range? (line 77) | def segment_overlaps_chunk_range?(segment)
    method create_track_from_points_array (line 89) | def create_track_from_points_array(points)
    method update_session_progress (line 119) | def update_session_progress(tracks_created)
    method mark_session_failed (line 124) | def mark_session_failed(error_message)
    method distance_threshold_meters (line 129) | def distance_threshold_meters
    method time_threshold_minutes (line 133) | def time_threshold_minutes

FILE: app/jobs/tracks/transportation_mode_recalculation_job.rb
  type Tracks (line 3) | module Tracks
    class TransportationModeRecalculationJob (line 4) | class TransportationModeRecalculationJob < ApplicationJob
      method perform (line 8) | def perform(user_id)
      method reprocess_all_tracks (line 21) | def reprocess_all_tracks

FILE: app/jobs/transportation_modes/backfill_job.rb
  type TransportationModes (line 3) | module TransportationModes
    class BackfillJob (line 13) | class BackfillJob < ApplicationJob
      method perform (line 18) | def perform(user_id, batch_size: DEFAULT_BATCH_SIZE)
      method extract_user_thresholds (line 35) | def extract_user_thresholds
      method tracks_to_process (line 40) | def tracks_to_process
      method process_track (line 49) | def process_track(track)
      method create_segments (line 75) | def create_segments(track, segment_data)

FILE: app/jobs/transportation_modes/import_backfill_job.rb
  type TransportationModes (line 3) | module TransportationModes
    class ImportBackfillJob (line 9) | class ImportBackfillJob < ApplicationJob
      method perform (line 12) | def perform(import_id)

FILE: app/jobs/trips/calculate_all_job.rb
  class Trips::CalculateAllJob (line 3) | class Trips::CalculateAllJob < ApplicationJob
    method perform (line 6) | def perform(trip_id, distance_unit = 'km')

FILE: app/jobs/trips/calculate_countries_job.rb
  class Trips::CalculateCountriesJob (line 3) | class Trips::CalculateCountriesJob < ApplicationJob
    method perform (line 6) | def perform(trip_id, distance_unit)
    method broadcast_update (line 17) | def broadcast_update(trip, distance_unit)

FILE: app/jobs/trips/calculate_distance_job.rb
  class Trips::CalculateDistanceJob (line 3) | class Trips::CalculateDistanceJob < ApplicationJob
    method perform (line 6) | def perform(trip_id, distance_unit)
    method broadcast_update (line 17) | def broadcast_update(trip, distance_unit)

FILE: app/jobs/trips/calculate_path_job.rb
  class Trips::CalculatePathJob (line 3) | class Trips::CalculatePathJob < ApplicationJob
    method perform (line 6) | def perform(trip_id)
    method broadcast_update (line 17) | def broadcast_update(trip)

FILE: app/jobs/users/destroy_job.rb
  class Users::DestroyJob (line 3) | class Users::DestroyJob < ApplicationJob
    method perform (line 8) | def perform(user_id)

FILE: app/jobs/users/digests/calculating_job.rb
  class Users::Digests::CalculatingJob (line 3) | class Users::Digests::CalculatingJob < ApplicationJob
    method perform (line 6) | def perform(user_id, year)
    method recalculate_monthly_stats (line 15) | def recalculate_monthly_stats(user_id, year)
    method create_digest_failed_notification (line 21) | def create_digest_failed_notification(user_id, error)

FILE: app/jobs/users/digests/email_sending_job.rb
  class Users::Digests::EmailSendingJob (line 3) | class Users::Digests::EmailSendingJob < ApplicationJob
    method perform (line 6) | def perform(user_id, year)
    method should_send_email? (line 20) | def should_send_email?(user, digest)

FILE: app/jobs/users/digests/year_end_scheduling_job.rb
  class Users::Digests::YearEndSchedulingJob (line 3) | class Users::Digests::YearEndSchedulingJob < ApplicationJob
    method perform (line 6) | def perform

FILE: app/jobs/users/export_data_job.rb
  class Users::ExportDataJob (line 3) | class Users::ExportDataJob < ApplicationJob
    method perform (line 8) | def perform(user_id)

FILE: app/jobs/users/import_data_job.rb
  class Users::ImportDataJob (line 3) | class Users::ImportDataJob < ApplicationJob
    method perform (line 8) | def perform(import_id)
    method handle_import_failure (line 35) | def handle_import_failure(import, user, error)
    method cleanup_archive (line 43) | def cleanup_archive(archive_path)
    method download_import_archive (line 50) | def download_import_archive(import)
    method create_import_failed_notification (line 66) | def create_import_failed_notification(user, error)

FILE: app/jobs/users/mailer_sending_job.rb
  class Users::MailerSendingJob (line 3) | class Users::MailerSendingJob < ApplicationJob
    method perform (line 6) | def perform(user_id, email_type, **options)
    method should_skip_email? (line 18) | def should_skip_email?(user, email_type)

FILE: app/jobs/users/recalculate_data_job.rb
  class Users::RecalculateDataJob (line 6) | class Users::RecalculateDataJob < ApplicationJob
    method perform (line 11) | def perform(user_id, year: nil)
    method determine_years (line 39) | def determine_years
    method recalculate_stats (line 47) | def recalculate_stats(years_to_process)
    method recalculate_tracks (line 57) | def recalculate_tracks(years_to_process)
    method recalculate_digests (line 73) | def recalculate_digests(years_to_process)
    method create_success_notification (line 81) | def create_success_notification(years_to_process)
    method create_failure_notification (line 92) | def create_failure_notification(error)

FILE: app/jobs/users/trial_webhook_job.rb
  class Users::TrialWebhookJob (line 3) | class Users::TrialWebhookJob < ApplicationJob
    method perform (line 6) | def perform(user_id)

FILE: app/jobs/visit_suggesting_job.rb
  class VisitSuggestingJob (line 3) | class VisitSuggestingJob < ApplicationJob
    method perform (line 10) | def perform(user_id:, start_at:, end_at:)
    method parse_date (line 29) | def parse_date(date)

FILE: app/mailers/application_mailer.rb
  class ApplicationMailer (line 3) | class ApplicationMailer < ActionMailer::Base

FILE: app/mailers/family_mailer.rb
  class FamilyMailer (line 3) | class FamilyMailer < ApplicationMailer
    method invitation (line 4) | def invitation(invitation)
    method location_request (line 16) | def location_request(request)
    method member_joined (line 28) | def member_joined(family, user)

FILE: app/mailers/users/digests_mailer.rb
  class Users::DigestsMailer (line 3) | class Users::DigestsMailer < ApplicationMailer
    method year_end_digest (line 7) | def year_end_digest

FILE: app/mailers/users_mailer.rb
  class UsersMailer (line 3) | class UsersMailer < ApplicationMailer
    method welcome (line 4) | def welcome
    method explore_features (line 11) | def explore_features
    method trial_expires_soon (line 18) | def trial_expires_soon
    method trial_expired (line 25) | def trial_expired
    method post_trial_reminder_early (line 32) | def post_trial_reminder_early
    method post_trial_reminder_late (line 39) | def post_trial_reminder_late
    method archival_approaching (line 46) | def archival_approaching

FILE: app/models/application_record.rb
  class ApplicationRecord (line 3) | class ApplicationRecord < ActiveRecord::Base

FILE: app/models/area.rb
  class Area (line 3) | class Area < ApplicationRecord
    method center (line 14) | def center = [latitude.to_f, longitude.to_f]

FILE: app/models/concerns/archivable.rb
  type Archivable (line 3) | module Archivable
    function raw_data_with_archive (line 20) | def raw_data_with_archive
    function restore_raw_data! (line 27) | def restore_raw_data!(value)
    function fetch_archived_raw_data (line 37) | def fetch_archived_raw_data
    function check_temporary_restore_cache (line 47) | def check_temporary_restore_cache
    function fetch_from_archive_file (line 55) | def fetch_from_archive_file
    function handle_archive_fetch_error (line 78) | def handle_archive_fetch_error(error)

FILE: app/models/concerns/calculateable.rb
  type Calculateable (line 3) | module Calculateable
    function calculate_path (line 6) | def calculate_path
    function calculate_distance (line 14) | def calculate_distance
    function recalculate_path! (line 20) | def recalculate_path!
    function recalculate_distance! (line 25) | def recalculate_distance!
    function recalculate_path_and_distance! (line 30) | def recalculate_path_and_distance!
    function path_coordinates (line 38) | def path_coordinates
    function set_path_attributes (line 42) | def set_path_attributes(updated_path)
    function calculate_distance_from_coordinates (line 47) | def calculate_distance_from_coordinates
    function convert_distance_for_storage (line 52) | def convert_distance_for_storage(calculated_distance_meters)
    function track_model? (line 57) | def track_model?
    function save_if_changed! (line 61) | def save_if_changed!

FILE: app/models/concerns/distance_convertible.rb
  type DistanceConvertible (line 23) | module DistanceConvertible
    function distance_in_unit (line 26) | def distance_in_unit(unit)
    type ClassMethods (line 40) | module ClassMethods
      function convert_distance (line 41) | def convert_distance(distance_meters, unit)

FILE: app/models/concerns/distanceable.rb
  type Distanceable (line 3) | module Distanceable
    type ClassMethods (line 6) | module ClassMethods
      function total_distance (line 7) | def total_distance(points = nil, unit = :km)
      function calculate_distance_for_array_geocoder (line 16) | def calculate_distance_for_array_geocoder(points, unit = :km)
      function calculate_distance_for_relation (line 102) | def calculate_distance_for_relation(unit)
      function calculate_distance_for_array (line 130) | def calculate_distance_for_array(points, unit = :km)
      function calculate_batch_distances (line 142) | def calculate_batch_distances(points)
    function distance_to (line 181) | def distance_to(other_point, unit = :km)
    function distance_to_geocoder (line 202) | def distance_to_geocoder(other_point, unit = :km)
    function extract_point (line 289) | def extract_point(point)

FILE: app/models/concerns/nearable.rb
  type Nearable (line 3) | module Nearable
    function near (line 10) | def near(*args)
    function with_distance (line 32) | def with_distance(*args)
    function extract_coordinates_and_options (line 54) | def extract_coordinates_and_options(*args)

FILE: app/models/concerns/omniauthable.rb
  type Omniauthable (line 3) | module Omniauthable
    function from_omniauth (line 7) | def from_omniauth(access_token)
    function oidc_auto_register_enabled? (line 41) | def oidc_auto_register_enabled?

FILE: app/models/concerns/plan_scopable.rb
  type PlanScopable (line 3) | module PlanScopable
    function plan_restricted? (line 6) | def plan_restricted?
    function data_window_start (line 10) | def data_window_start
    function scoped_points (line 14) | def scoped_points
    function scoped_tracks (line 20) | def scoped_tracks
    function scoped_visits (line 26) | def scoped_visits
    function scoped_stats (line 32) | def scoped_stats

FILE: app/models/concerns/point_validation.rb
  type PointValidation (line 3) | module PointValidation
    function point_exists? (line 6) | def point_exists?(params, user_id)

FILE: app/models/concerns/soft_deletable.rb
  type SoftDeletable (line 3) | module SoftDeletable
    type DeviseOverrides (line 11) | module DeviseOverrides
      function active_for_authentication? (line 12) | def active_for_authentication?
      function inactive_message (line 16) | def inactive_message
    function deleted? (line 28) | def deleted?
    function mark_as_deleted! (line 32) | def mark_as_deleted!
    function mark_as_deleted_atomically! (line 39) | def mark_as_deleted_atomically!
    function reload (line 55) | def reload(options = nil)
    function destroy (line 63) | def destroy

FILE: app/models/concerns/taggable.rb
  type Taggable (line 3) | module Taggable
    function add_tag (line 27) | def add_tag(tag)
    function remove_tag (line 31) | def remove_tag(tag)
    function tag_names (line 35) | def tag_names
    function tagged_with? (line 39) | def tagged_with?(tag)

FILE: app/models/concerns/user_family.rb
  type UserFamily (line 3) | module UserFamily
    function in_family? (line 22) | def in_family?
    function family_owner? (line 26) | def family_owner?
    function can_delete_account? (line 30) | def can_delete_account?
    function family_sharing_enabled? (line 37) | def family_sharing_enabled?
    function update_family_location_sharing! (line 48) | def update_family_location_sharing!(enabled, duration: nil, share_hist...
    function family_sharing_expires_at (line 87) | def family_sharing_expires_at
    function family_sharing_duration (line 97) | def family_sharing_duration
    function family_sharing_started_at (line 101) | def family_sharing_started_at
    function family_share_history? (line 110) | def family_share_history?
    function family_history_window (line 114) | def family_history_window
    function family_history_points (line 121) | def family_history_points(start_at:, end_at:)
    function latest_location_for_family (line 148) | def latest_location_for_family
    function validate_history_window (line 171) | def validate_history_window(window)

FILE: app/models/country.rb
  class Country (line 3) | class Country < ApplicationRecord
    method containing_point (line 8) | def self.containing_point(lon, lat)
    method names_to_iso_a2 (line 14) | def self.names_to_iso_a2

FILE: app/models/export.rb
  class Export (line 3) | class Export < ApplicationRecord
    method process! (line 19) | def process!
    method migrate_to_new_storage (line 23) | def migrate_to_new_storage
    method set_processing_started_at (line 34) | def set_processing_started_at
    method status_changed_to_processing? (line 38) | def status_changed_to_processing?
    method remove_attached_file (line 42) | def remove_attached_file

FILE: app/models/family.rb
  class Family (line 3) | class Family < ApplicationRecord
    method can_add_members? (line 13) | def can_add_members?
    method member_count (line 19) | def member_count
    method pending_invitations_count (line 23) | def pending_invitations_count
    method owners (line 27) | def owners
    method owner (line 32) | def owner
    method full? (line 36) | def full?
    method active_invitations (line 42) | def active_invitations
    method clear_member_cache! (line 46) | def clear_member_cache!

FILE: app/models/family/invitation.rb
  class Family::Invitation (line 3) | class Family::Invitation < ApplicationRecord
    method expired? (line 25) | def expired?
    method can_be_accepted? (line 29) | def can_be_accepted?
    method generate_token (line 35) | def generate_token
    method set_expiry (line 39) | def set_expiry
    method clear_family_cache (line 43) | def clear_family_cache

FILE: app/models/family/location_request.rb
  class Family::LocationRequest (line 3) | class Family::LocationRequest < ApplicationRecord
    method requester_cannot_be_target (line 24) | def requester_cannot_be_target
    method set_defaults (line 30) | def set_defaults

FILE: app/models/family/membership.rb
  class Family::Membership (line 3) | class Family::Membership < ApplicationRecord
    method clear_family_cache (line 21) | def clear_family_cache
    method cleanup_on_departure (line 25) | def cleanup_on_departure

FILE: app/models/import.rb
  class Import (line 3) | class Import < ApplicationRecord
    method process! (line 30) | def process!
    method process_user_data_archive! (line 38) | def process_user_data_archive!
    method reverse_geocoded_points_count (line 42) | def reverse_geocoded_points_count
    method years_and_months_tracked (line 46) | def years_and_months_tracked
    method migrate_to_new_storage (line 53) | def migrate_to_new_storage
    method set_processing_started_at (line 63) | def set_processing_started_at
    method status_changed_to_processing? (line 67) | def status_changed_to_processing?
    method remove_attached_file (line 71) | def remove_attached_file
    method file_size_within_limit (line 75) | def file_size_within_limit
    method import_count_within_limit (line 83) | def import_count_within_limit
    method recalculate_stats (line 92) | def recalculate_stats

FILE: app/models/notification.rb
  class Notification (line 3) | class Notification < ApplicationRecord
    method read? (line 14) | def read?
    method broadcast_notification (line 20) | def broadcast_notification

FILE: app/models/place.rb
  class Place (line 3) | class Place < ApplicationRecord
    method lon (line 26) | def lon
    method lat (line 30) | def lat
    method osm_id (line 34) | def osm_id
    method osm_key (line 38) | def osm_key
    method osm_value (line 42) | def osm_value
    method osm_type (line 46) | def osm_type
    method build_lonlat (line 52) | def build_lonlat

FILE: app/models/place_visit.rb
  class PlaceVisit (line 3) | class PlaceVisit < ApplicationRecord

FILE: app/models/point.rb
  class Point (line 3) | class Point < ApplicationRecord
    method without_raw_data (line 40) | def self.without_raw_data
    method recorded_at (line 44) | def recorded_at
    method async_reverse_geocode (line 48) | def async_reverse_geocode
    method reverse_geocoded? (line 54) | def reverse_geocoded?
    method lon (line 58) | def lon
    method lat (line 62) | def lat
    method found_in_country (line 66) | def found_in_country
    method country_name (line 70) | def country_name
    method broadcast_coordinates (line 78) | def broadcast_coordinates
    method should_broadcast_to_family? (line 98) | def should_broadcast_to_family?
    method broadcast_to_family (line 106) | def broadcast_to_family
    method set_country (line 121) | def set_country
    method recalculate_track (line 126) | def recalculate_track

FILE: app/models/points/raw_data_archive.rb
  type Points (line 3) | module Points
    class RawDataArchive (line 4) | class RawDataArchive < ApplicationRecord
      method month_display (line 29) | def month_display
      method filename (line 33) | def filename
      method size_mb (line 37) | def size_mb
      method verified? (line 43) | def verified?
      method count_mismatch? (line 47) | def count_mismatch?
      method metadata_contains_expected_and_actual_counts (line 60) | def metadata_contains_expected_and_actual_counts

FILE: app/models/stat.rb
  class Stat (line 3) | class Stat < ApplicationRecord
    method distance_by_day (line 12) | def distance_by_day
    method year_distance (line 17) | def self.year_distance(year, user)
    method points (line 28) | def points
    method sharing_enabled? (line 35) | def sharing_enabled?
    method sharing_expired? (line 39) | def sharing_expired?
    method public_accessible? (line 55) | def public_accessible?
    method hexagons_available? (line 59) | def hexagons_available?
    method generate_new_sharing_uuid! (line 65) | def generate_new_sharing_uuid!
    method enable_sharing! (line 69) | def enable_sharing!(expiration: '1h')
    method disable_sharing! (line 91) | def disable_sharing!
    method calculate_data_bounds (line 101) | def calculate_data_bounds
    method process! (line 130) | def process!
    method generate_sharing_uuid (line 136) | def generate_sharing_uuid
    method timespan (line 140) | def timespan
    method calculate_daily_distances (line 144) | def calculate_daily_distances(monthly_points)
    method user_timezone (line 148) | def user_timezone

FILE: app/models/tag.rb
  class Tag (line 3) | class Tag < ApplicationRecord
    method privacy_zone? (line 22) | def privacy_zone?
    method icon_is_not_ascii_letter (line 28) | def icon_is_not_ascii_letter

FILE: app/models/tagging.rb
  class Tagging (line 3) | class Tagging < ApplicationRecord

FILE: app/models/track.rb
  class Track (line 3) | class Track < ApplicationRecord
    method last_for_day (line 41) | def self.last_for_day(user, day)
    method segment_points_in_sql (line 51) | def self.segment_points_in_sql(user_id, start_timestamp, end_timestamp...
    method get_segments_with_points (line 129) | def self.get_segments_with_points(user_id, start_timestamp, end_timest...
    method parse_postgres_array (line 154) | def self.parse_postgres_array(pg_array_string)
    method activity_breakdown (line 161) | def activity_breakdown
    method update_dominant_mode! (line 165) | def update_dominant_mode!
    method broadcast_geojson_updated (line 173) | def broadcast_geojson_updated
    method broadcast_track_created (line 186) | def broadcast_track_created
    method broadcast_track_updated (line 190) | def broadcast_track_updated
    method broadcast_track_destroyed (line 194) | def broadcast_track_destroyed
    method broadcast_track_update (line 198) | def broadcast_track_update(action)

FILE: app/models/track_segment.rb
  class TrackSegment (line 3) | class TrackSegment < ApplicationRecord
    method end_index_greater_than_or_equal_to_start_index (line 24) | def end_index_greater_than_or_equal_to_start_index

FILE: app/models/trip.rb
  class Trip (line 3) | class Trip < ApplicationRecord
    method enqueue_calculation_jobs (line 17) | def enqueue_calculation_jobs
    method points (line 21) | def points
    method photo_previews (line 25) | def photo_previews
    method photo_sources (line 29) | def photo_sources
    method calculate_countries (line 33) | def calculate_countries
    method photos (line 39) | def photos
    method select_dominant_orientation (line 43) | def select_dominant_orientation(photos)
    method started_at_before_ended_at (line 52) | def started_at_before_ended_at

FILE: app/models/user.rb
  class User (line 3) | class User < ApplicationRecord
    method safe_settings (line 45) | def safe_settings
    method countries_visited (line 49) | def countries_visited
    method cities_visited (line 55) | def cities_visited
    method total_distance (line 61) | def total_distance
    method total_countries (line 68) | def total_countries
    method total_cities (line 72) | def total_cities
    method total_reverse_geocoded_points (line 76) | def total_reverse_geocoded_points
    method total_reverse_geocoded_points_without_data (line 80) | def total_reverse_geocoded_points_without_data
    method immich_integration_configured? (line 84) | def immich_integration_configured?
    method photoprism_integration_configured? (line 88) | def photoprism_integration_configured?
    method years_tracked (line 92) | def years_tracked
    method can_subscribe? (line 114) | def can_subscribe?
    method generate_subscription_token (line 118) | def generate_subscription_token
    method export_data (line 130) | def export_data
    method trial_state? (line 134) | def trial_state?
    method countries_visited_uncached (line 142) | def countries_visited_uncached
    method cities_visited_uncached (line 161) | def cities_visited_uncached
    method home_place_coordinates (line 183) | def home_place_coordinates
    method supporter? (line 194) | def supporter?
    method supporter_platform (line 198) | def supporter_platform
    method supporter_info (line 202) | def supporter_info
    method create_api_key (line 210) | def create_api_key
    method activate (line 216) | def activate
    method sanitize_input (line 220) | def sanitize_input
    method start_trial (line 226) | def start_trial
    method schedule_welcome_emails (line 233) | def schedule_welcome_emails
    method schedule_post_trial_emails (line 241) | def schedule_post_trial_emails

FILE: app/models/users/digest.rb
  class Users::Digest (line 3) | class Users::Digest < ApplicationRecord
    method sharing_enabled? (line 22) | def sharing_enabled?
    method sharing_expired? (line 26) | def sharing_expired?
    method public_accessible? (line 42) | def public_accessible?
    method generate_new_sharing_uuid! (line 46) | def generate_new_sharing_uuid!
    method enable_sharing! (line 50) | def enable_sharing!(expiration: '24h')
    method disable_sharing! (line 71) | def disable_sharing!
    method countries_count (line 81) | def countries_count
    method cities_count (line 87) | def cities_count
    method first_time_countries (line 93) | def first_time_countries
    method first_time_cities (line 97) | def first_time_cities
    method top_countries_by_time (line 101) | def top_countries_by_time
    method top_cities_by_time (line 105) | def top_cities_by_time
    method yoy_distance_change (line 109) | def yoy_distance_change
    method yoy_countries_change (line 113) | def yoy_countries_change
    method yoy_cities_change (line 117) | def yoy_cities_change
    method previous_year (line 121) | def previous_year
    method total_countries_all_time (line 125) | def total_countries_all_time
    method total_cities_all_time (line 129) | def total_cities_all_time
    method total_distance_all_time (line 133) | def total_distance_all_time
    method daily_distances (line 140) | def daily_distances
    method active_days_count (line 144) | def active_days_count
    method days_in_month (line 150) | def days_in_month
    method weekly_pattern (line 156) | def weekly_pattern
    method mom_distance_change (line 168) | def mom_distance_change
    method mom_countries_change (line 172) | def mom_countries_change
    method mom_cities_change (line 176) | def mom_cities_change
    method time_of_day_distribution (line 181) | def time_of_day_distribution
    method seasonality (line 185) | def seasonality
    method day_of_week_distances (line 189) | def day_of_week_distances
    method activity_breakdown (line 193) | def activity_breakdown
    method previous_month_value (line 197) | def previous_month_value
    method previous_month_year (line 201) | def previous_month_year
    method month_name (line 205) | def month_name
    method untracked_days (line 211) | def untracked_days
    method distance_km (line 216) | def distance_km
    method distance_comparison_text (line 220) | def distance_comparison_text
    method generate_sharing_uuid (line 232) | def generate_sharing_uuid
    method total_tracked_days (line 236) | def total_tracked_days
    method total_tracked_minutes (line 240) | def total_tracked_minutes

FILE: app/models/visit.rb
  class Visit (line 3) | class Visit < ApplicationRecord
    method coordinates (line 17) | def coordinates
    method default_name (line 21) | def default_name
    method default_radius (line 26) | def default_radius
    method center (line 38) | def center
    method center_from_points (line 48) | def center_from_points
    method async_reverse_geocode (line 58) | def async_reverse_geocode

FILE: app/models/visit_draft.rb
  class VisitDraft (line 3) | class VisitDraft
    method initialize (line 6) | def initialize(start_time)
    method add_point (line 12) | def add_point(point)
    method duration_in_minutes (line 17) | def duration_in_minutes
    method valid? (line 21) | def valid?

FILE: app/policies/application_policy.rb
  class ApplicationPolicy (line 3) | class ApplicationPolicy
    method initialize (line 6) | def initialize(user, record)
    method index? (line 11) | def index?
    method show? (line 15) | def show?
    method create? (line 19) | def create?
    method new? (line 23) | def new?
    method update? (line 27) | def update?
    method edit? (line 31) | def edit?
    method destroy? (line 35) | def destroy?
    class Scope (line 39) | class Scope
      method initialize (line 40) | def initialize(user, scope)
      method resolve (line 45) | def resolve

FILE: app/policies/family/invitation_policy.rb
  class Family::InvitationPolicy (line 3) | class Family::InvitationPolicy < ApplicationPolicy
    method create? (line 4) | def create?
    method accept? (line 10) | def accept?
    method destroy? (line 16) | def destroy?

FILE: app/policies/family/membership_policy.rb
  class Family::MembershipPolicy (line 3) | class Family::MembershipPolicy < ApplicationPolicy
    method create? (line 4) | def create?
    method destroy? (line 11) | def destroy?

FILE: app/policies/family_policy.rb
  class FamilyPolicy (line 3) | class FamilyPolicy < ApplicationPolicy
    method show? (line 4) | def show?
    method create? (line 8) | def create?
    method update? (line 17) | def update?
    method destroy? (line 21) | def destroy?
    method leave? (line 25) | def leave?
    method invite? (line 29) | def invite?
    method manage_invitations? (line 33) | def manage_invitations?
    method update_location_sharing? (line 37) | def update_location_sharing?
    method family_owner_with_members? (line 43) | def family_owner_with_members?

FILE: app/policies/import_policy.rb
  class ImportPolicy (line 3) | class ImportPolicy < ApplicationPolicy
    method index? (line 5) | def index?
    method show? (line 10) | def show?
    method new? (line 15) | def new?
    method create? (line 19) | def create?
    method edit? (line 24) | def edit?
    method update? (line 28) | def update?
    method destroy? (line 33) | def destroy?
    class Scope (line 37) | class Scope < ApplicationPolicy::Scope
      method resolve (line 38) | def resolve

FILE: app/policies/insights_policy.rb
  class InsightsPolicy (line 3) | class InsightsPolicy < ApplicationPolicy
    method index? (line 4) | def index?
    method details? (line 8) | def details?

FILE: app/policies/place_policy.rb
  class PlacePolicy (line 3) | class PlacePolicy < ApplicationPolicy
    class Scope (line 4) | class Scope < Scope
      method resolve (line 5) | def resolve
    method index? (line 10) | def index?
    method show? (line 14) | def show?
    method create? (line 18) | def create?
    method new? (line 22) | def new?
    method update? (line 26) | def update?
    method edit? (line 30) | def edit?
    method destroy? (line 34) | def destroy?
    method nearby? (line 38) | def nearby?
    method owner? (line 44) | def owner?

FILE: app/policies/tag_policy.rb
  class TagPolicy (line 3) | class TagPolicy < ApplicationPolicy
    class Scope (line 4) | class Scope < Scope
      method resolve (line 5) | def resolve
    method index? (line 10) | def index?
    method show? (line 14) | def show?
    method create? (line 18) | def create?
    method new? (line 22) | def new?
    method update? (line 26) | def update?
    method edit? (line 30) | def edit?
    method destroy? (line 34) | def destroy?
    method owner? (line 40) | def owner?

FILE: app/queries/stats/daily_distance_query.rb
  class Stats::DailyDistanceQuery (line 3) | class Stats::DailyDistanceQuery
    method initialize (line 4) | def initialize(monthly_points, timespan, timezone = nil)
    method call (line 10) | def call
    method daily_distances (line 21) | def daily_distances(monthly_points)
    method distance_by_day_map (line 57) | def distance_by_day_map(daily_distances)
    method convert_to_daily_distances (line 63) | def convert_to_daily_distances(distance_by_day_map)
    method validate_timezone (line 72) | def validate_timezone(timezone)

FILE: app/queries/stats/time_of_day_query.rb
  type Stats (line 3) | module Stats
    class TimeOfDayQuery (line 4) | class TimeOfDayQuery
      method initialize (line 12) | def initialize(user, year, month = nil, timezone = 'UTC')
      method call (line 19) | def call
      method execute_query (line 28) | def execute_query
      method start_timestamp (line 55) | def start_timestamp
      method end_timestamp (line 63) | def end_timestamp
      method normalize_to_percentages (line 71) | def normalize_to_percentages(result)
      method empty_result (line 81) | def empty_result
      method validate_timezone (line 85) | def validate_timezone(timezone_name)

FILE: app/queries/stats_query.rb
  class StatsQuery (line 3) | class StatsQuery
    method initialize (line 4) | def initialize(user)
    method points_stats (line 8) | def points_stats
    method cached_points_geocoded_stats (line 20) | def cached_points_geocoded_stats

FILE: app/queries/tracks/index_query.rb
  class Tracks::IndexQuery (line 3) | class Tracks::IndexQuery
    method initialize (line 6) | def initialize(user:, params: {})
    method call (line 11) | def call
    method pagination_headers (line 22) | def pagination_headers(paginated_relation)
    method normalize_params (line 34) | def normalize_params(params)
    method page_param (line 44) | def page_param
    method per_page_param (line 49) | def per_page_param
    method apply_date_range (line 54) | def apply_date_range(scope)
    method parse_timestamp (line 64) | def parse_timestamp(value)

FILE: app/serializers/api/digest_detail_serializer.rb
  class Api::DigestDetailSerializer (line 3) | class Api::DigestDetailSerializer
    method initialize (line 4) | def initialize(digest, distance_unit:)
    method call (line 9) | def call
    method serialize_monthly_distances (line 29) | def serialize_monthly_distances
    method serialize_distance (line 38) | def serialize_distance
    method serialize_toponyms (line 48) | def serialize_toponyms
    method serialize_year_over_year (line 63) | def serialize_year_over_year
    method serialize_all_time_stats (line 74) | def serialize_all_time_stats
    method serialize_travel_patterns (line 83) | def serialize_travel_patterns

FILE: app/serializers/api/digest_list_serializer.rb
  class Api::DigestListSerializer (line 3) | class Api::DigestListSerializer
    method initialize (line 4) | def initialize(digests:, available_years:)
    method call (line 9) | def call
    method serialize_digest (line 20) | def serialize_digest(digest)

FILE: app/serializers/api/insights_details_serializer.rb
  class Api::InsightsDetailsSerializer (line 3) | class Api::InsightsDetailsSerializer
    method initialize (line 4) | def initialize(year:, comparison:, travel_patterns:)
    method call (line 10) | def call
    method serialize_comparison (line 22) | def serialize_comparison
    method serialize_travel_patterns (line 34) | def serialize_travel_patterns

FILE: app/serializers/api/insights_overview_serializer.rb
  class Api::InsightsOverviewSerializer (line 3) | class Api::InsightsOverviewSerializer
    method initialize (line 4) | def initialize(year:, available_years:, totals:, heatmap:, distance_un...
    method call (line 12) | def call
    method serialize_totals (line 25) | def serialize_totals
    method serialize_heatmap (line 37) | def serialize_heatmap

FILE: app/serializers/api/location_search_result_serializer.rb
  class Api::LocationSearchResultSerializer (line 3) | class Api::LocationSearchResultSerializer
    method initialize (line 4) | def initialize(search_result)
    method call (line 8) | def call
    method serialize_locations (line 19) | def serialize_locations(locations)
    method serialize_visits (line 33) | def serialize_visits(visits)

FILE: app/serializers/api/photo_serializer.rb
  class Api::PhotoSerializer (line 3) | class Api::PhotoSerializer
    method initialize (line 4) | def initialize(photo, source)
    method call (line 9) | def call
    method id (line 29) | def id
    method latitude (line 33) | def latitude
    method longitude (line 37) | def longitude
    method local_date_time (line 41) | def local_date_time
    method original_file_name (line 45) | def original_file_name
    method city (line 49) | def city
    method state (line 53) | def state
    method country (line 57) | def country
    method type (line 61) | def type
    method orientation (line 65) | def orientation

FILE: app/serializers/api/place_serializer.rb
  class Api::PlaceSerializer (line 3) | class Api::PlaceSerializer
    method initialize (line 4) | def initialize(place)
    method call (line 8) | def call

FILE: app/serializers/api/point_serializer.rb
  class Api::PointSerializer (line 3) | class Api::PointSerializer
    method initialize (line 9) | def initialize(point)
    method call (line 13) | def call

FILE: app/serializers/api/slim_point_serializer.rb
  class Api::SlimPointSerializer (line 3) | class Api::SlimPointSerializer
    method initialize (line 4) | def initialize(point)
    method call (line 8) | def call

FILE: app/serializers/api/user_serializer.rb
  class Api::UserSerializer (line 3) | class Api::UserSerializer
    method initialize (line 4) | def initialize(user)
    method call (line 8) | def call
    method settings (line 28) | def settings
    method subscription (line 51) | def subscription

FILE: app/serializers/api/visit_serializer.rb
  class Api::VisitSerializer (line 3) | class Api::VisitSerializer
    method initialize (line 4) | def initialize(visit)
    method call (line 8) | def call

FILE: app/serializers/export_serializer.rb
  class ExportSerializer (line 3) | class ExportSerializer
    method initialize (line 6) | def initialize(points, user_email)
    method call (line 11) | def call
    method export_points (line 17) | def export_points
    method export_point (line 23) | def export_point(point)
    method battery_status (line 47) | def battery_status(point)
    method trigger (line 56) | def trigger(point)
    method connection (line 69) | def connection(point)

FILE: app/serializers/exports/point_geojson_serializer.rb
  class Exports::PointGeojsonSerializer (line 3) | class Exports::PointGeojsonSerializer
    method initialize (line 6) | def initialize(points_scope)
    method call (line 10) | def call
    method write_to (line 24) | def write_to(io)

FILE: app/serializers/exports/point_gpx_serializer.rb
  class Exports::PointGpxSerializer (line 3) | class Exports::PointGpxSerializer
    method initialize (line 6) | def initialize(points_scope, name)
    method call (line 11) | def call
    method write_to (line 25) | def write_to(io)
    method write_header (line 37) | def write_header(io)
    method write_footer (line 47) | def write_footer(io)
    method write_trackpoint (line 55) | def write_trackpoint(io, point)
    method escape_xml (line 74) | def escape_xml(str)

FILE: app/serializers/point_serializer.rb
  class PointSerializer (line 3) | class PointSerializer
    method initialize (line 9) | def initialize(point)
    method call (line 13) | def call

FILE: app/serializers/points/geojson_serializer.rb
  class Points::GeojsonSerializer (line 3) | class Points::GeojsonSerializer
    method initialize (line 4) | def initialize(points)
    method call (line 8) | def call

FILE: app/serializers/points/gpx_serializer.rb
  class EnhancedGpxFile (line 4) | class EnhancedGpxFile < GPX::GPXFile
    method initialize (line 5) | def initialize(name, xml_string)
    method to_s (line 10) | def to_s
  class Points::GpxSerializer (line 15) | class Points::GpxSerializer
    method initialize (line 16) | def initialize(points, name)
    method call (line 21) | def call
    method create_base_gpx_file (line 33) | def create_base_gpx_file
    method add_track_points_to_gpx (line 44) | def add_track_points_to_gpx(gpx_file)
    method create_track_point (line 53) | def create_track_point(point)
    method build_track_point_attributes (line 58) | def build_track_point_attributes(point)
    method enhance_gpx_with_speed_and_course (line 67) | def enhance_gpx_with_speed_and_course(gpx_xml)
    method add_gpx_namespace (line 72) | def add_gpx_namespace(gpx_xml)
    method enhance_trackpoints_with_speed_and_course (line 76) | def enhance_trackpoints_with_speed_and_course(xml_string)
    method enhance_single_trackpoint (line 85) | def enhance_single_trackpoint(trkpt_xml, point)
    method add_speed_to_trackpoint (line 90) | def add_speed_to_trackpoint(trkpt_xml, point)
    method add_course_to_trackpoint (line 96) | def add_course_to_trackpoint(trkpt_xml, point)
    method should_include_speed? (line 103) | def should_include_speed?(point)
    method should_include_course? (line 107) | def should_include_course?(point)

FILE: app/serializers/stats_serializer.rb
  class StatsSerializer (line 3) | class StatsSerializer
    method initialize (line 6) | def initialize(user)
    method call (line 10) | def call
    method total_distance_km (line 23) | def total_distance_km
    method reverse_geocoded_points (line 29) | def reverse_geocoded_points
    method yearly_stats (line 33) | def yearly_stats
    method stats_distance_km (line 45) | def stats_distance_km(stats)
    method monthly_distance (line 51) | def monthly_distance(year, stats)
    method distance_km (line 59) | def distance_km(month, year, stats)

FILE: app/serializers/tag_serializer.rb
  class TagSerializer (line 3) | class TagSerializer
    method initialize (line 4) | def initialize(tag)
    method call (line 8) | def call
    method places (line 23) | def places

FILE: app/serializers/track_serializer.rb
  class TrackSerializer (line 3) | class TrackSerializer
    method initialize (line 4) | def initialize(track)
    method call (line 8) | def call

FILE: app/serializers/tracks/geojson_serializer.rb
  class Tracks::GeojsonSerializer (line 3) | class Tracks::GeojsonSerializer
    method initialize (line 36) | def initialize(tracks, include_segments: false)
    method call (line 41) | def call
    method feature_for (line 52) | def feature_for(track)
    method properties_for (line 60) | def properties_for(track)
    method base_properties (line 64) | def base_properties(track)
    method segment_properties (line 76) | def segment_properties(track)
    method segments_for (line 91) | def segments_for(track)
    method serialize_segment (line 107) | def serialize_segment(segment, start_time = nil)
    method segment_identity (line 113) | def segment_identity(segment)
    method segment_stats (line 123) | def segment_stats(segment)
    method segment_times (line 132) | def segment_times(segment, start_time)
    method mode_timeline_for (line 142) | def mode_timeline_for(track)
    method emoji_for_mode (line 164) | def emoji_for_mode(mode)
    method color_for_mode (line 168) | def color_for_mode(mode)
    method geometry_for (line 172) | def geometry_for(track)

FILE: app/serializers/tracks_serializer.rb
  class TracksSerializer (line 3) | class TracksSerializer
    method initialize (line 4) | def initialize(user, track_ids)
    method call (line 9) | def call

FILE: app/services/areas/visits/create.rb
  class Areas::Visits::Create (line 3) | class Areas::Visits::Create
    method initialize (line 6) | def initialize(user, areas)
    method call (line 13) | def call
    method area_visits (line 20) | def area_visits(area)
    method distinct_months_for_area (line 40) | def distinct_months_for_area(area)
    method area_points_for_month (line 59) | def area_points_for_month(area, month)
    method create_or_update_visit (line 74) | def create_or_update_visit(area, time_range, visit_points)
    method find_or_initialize_visit (line 93) | def find_or_initialize_visit(area_id, timestamp)

FILE: app/services/cache/clean.rb
  class Cache::Clean (line 3) | class Cache::Clean
    method call (line 5) | def call
    method delete_control_flag (line 23) | def delete_control_flag
    method delete_version_cache (line 27) | def delete_version_cache
    method delete_years_tracked_cache (line 31) | def delete_years_tracked_cache(user)
    method delete_points_geocoded_stats_cache (line 35) | def delete_points_geocoded_stats_cache(user)
    method delete_countries_cities_cache (line 39) | def delete_countries_cities_cache(user)
    method delete_total_distance_cache (line 44) | def delete_total_distance_cache(user)
    method delete_insights_digest_cache (line 48) | def delete_insights_digest_cache(user)

FILE: app/services/cache/invalidate_user_caches.rb
  class Cache::InvalidateUserCaches (line 3) | class Cache::InvalidateUserCaches
    method initialize (line 9) | def initialize(user_id, year: nil)
    method call (line 14) | def call
    method invalidate_countries_visited (line 22) | def invalidate_countries_visited
    method invalidate_cities_visited (line 26) | def invalidate_cities_visited
    method invalidate_points_geocoded_stats (line 30) | def invalidate_points_geocoded_stats
    method invalidate_total_distance (line 34) | def invalidate_total_distance
    method invalidate_insights_digest (line 38) | def invalidate_insights_digest

FILE: app/services/cache/preheat_insights_digests.rb
  class Cache::PreheatInsightsDigests (line 3) | class Cache::PreheatInsightsDigests
    method initialize (line 7) | def initialize(user)
    method call (line 11) | def call
    method recent_years_with_stats (line 24) | def recent_years_with_stats
    method preheat_year (line 29) | def preheat_year(year)
    method digest_stale? (line 42) | def digest_stale?(digest, year)

FILE: app/services/check_app_version.rb
  class CheckAppVersion (line 3) | class CheckAppVersion
    method initialize (line 6) | def initialize
    method call (line 10) | def call
    method latest_version (line 20) | def latest_version

FILE: app/services/concerns/ssl_configurable.rb
  type SslConfigurable (line 3) | module SslConfigurable
    function ssl_verification_enabled? (line 8) | def ssl_verification_enabled?(user, service_type)
    function http_options_with_ssl (line 15) | def http_options_with_ssl(user, service_type, base_options = {})
    function http_options_with_ssl_flag (line 20) | def http_options_with_ssl_flag(skip_ssl_verification, base_options = {})

FILE: app/services/countries/iso_code_mapper.rb
  class Countries::IsoCodeMapper (line 3) | class Countries::IsoCodeMapper
    method iso_a3_from_a2 (line 289) | def self.iso_a3_from_a2(iso_a2)
    method iso_codes_from_country_name (line 296) | def self.iso_codes_from_country_name(country_name)
    method fallback_codes_from_country_name (line 325) | def self.fallback_codes_from_country_name(country_name)
    method standardize_country_name (line 340) | def self.standardize_country_name(country_name)
    method country_flag (line 365) | def self.country_flag(iso_a2)
    method country_by_iso2 (line 372) | def self.country_by_iso2(iso_a2)
    method country_by_name (line 378) | def self.country_by_name(country_name)
    method all_countries (line 386) | def self.all_countries
    method find_country_by_name (line 390) | def self.find_country_by_name(name)

FILE: app/services/countries_and_cities.rb
  class CountriesAndCities (line 3) | class CountriesAndCities
    method initialize (line 7) | def initialize(points, min_minutes_spent_in_city: 60, max_gap_minutes:...
    method call (line 13) | def call
    method canonical_country_name (line 25) | def canonical_country_name(point)
    method country_names_by_id (line 32) | def country_names_by_id
    method process_country_points (line 39) | def process_country_points(country_points)
    method create_city_data_if_valid (line 47) | def create_city_data_if_valid(city_points)
    method build_city_data (line 56) | def build_city_data(city, points_count, timestamps, duration)
    method calculate_duration_in_minutes (line 67) | def calculate_duration_in_minutes(timestamps)

FILE: app/services/exception_reporter.rb
  class ExceptionReporter (line 3) | class ExceptionReporter
    method call (line 4) | def self.call(exception, human_message = 'Exception reported')

FILE: app/services/exports/create.rb
  class Exports::Create (line 3) | class Exports::Create
    method initialize (line 4) | def initialize(export:)
    method call (line 12) | def call
    method time_framed_points (line 32) | def time_framed_points
    method build_export_tempfile (line 39) | def build_export_tempfile
    method notify_export_finished (line 47) | def notify_export_finished
    method notify_export_failed (line 56) | def notify_export_failed(error)
    method attach_export_file (line 65) | def attach_export_file(tempfile)
    method content_type (line 71) | def content_type

FILE: app/services/families/accept_invitation.rb
  type Families (line 3) | module Families
    class AcceptInvitation (line 4) | class AcceptInvitation
      method initialize (line 7) | def initialize(invitation:, user:)
      method call (line 13) | def call
      method can_accept? (line 39) | def can_accept?
      method validate_invitation (line 47) | def validate_invitation
      method validate_email_match (line 55) | def validate_email_match
      method validate_family_capacity (line 63) | def validate_family_capacity
      method create_membership (line 71) | def create_membership
      method update_invitation (line 79) | def update_invitation
      method send_notifications (line 83) | def send_notifications
      method send_user_notification (line 88) | def send_user_notification
      method send_owner_notification (line 97) | def send_owner_notification
      method handle_record_invalid_error (line 108) | def handle_record_invalid_error(error)
      method handle_generic_error (line 117) | def handle_generic_error(error)

FILE: app/services/families/create.rb
  type Families (line 3) | module Families
    class Create (line 4) | class Create
      method initialize (line 15) | def initialize(user:, name:)
      method call (line 21) | def call
      method validate_user_eligibility (line 49) | def validate_user_eligibility
      method validate_feature_access (line 63) | def validate_feature_access
      method can_create_family? (line 76) | def can_create_family?
      method create_family (line 84) | def create_family
      method create_owner_membership (line 88) | def create_owner_membership
      method send_notification (line 96) | def send_notification
      method handle_record_invalid_error (line 108) | def handle_record_invalid_error(error)
      method handle_uniqueness_error (line 117) | def handle_uniqueness_error(_error)
      method handle_generic_error (line 121) | def handle_generic_error(error)

FILE: app/services/families/create_location_request.rb
  class Families::CreateLocationRequest (line 3) | class Families::CreateLocationRequest
    method initialize (line 8) | def initialize(requester:, target_user:)
    method call (line 13) | def call
    method in_same_family? (line 34) | def in_same_family?
    method cooldown_active? (line 38) | def cooldown_active?
    method create_request! (line 46) | def create_request!
    method create_notification! (line 54) | def create_notification!(request)
    method enqueue_email (line 72) | def enqueue_email(request)
    method not_in_same_family_error (line 76) | def not_in_same_family_error
    method already_sharing_error (line 80) | def already_sharing_error
    method cooldown_error (line 85) | def cooldown_error

FILE: app/services/families/invite.rb
  type Families (line 3) | module Families
    class Invite (line 4) | class Invite
      method initialize (line 11) | def initialize(family:, email:, invited_by:)
      method call (line 17) | def call
      method error_message (line 39) | def error_message
      method invite_sendable? (line 48) | def invite_sendable?
      method add_error_and_false (line 60) | def add_error_and_false(attribute, message)
      method user_already_in_family? (line 65) | def user_already_in_family?
      method pending_invitation_exists? (line 71) | def pending_invitation_exists?
      method create_invitation (line 75) | def create_invitation
      method send_invitation_email (line 83) | def send_invitation_email(invitation)
      method send_notification (line 87) | def send_notification
      method handle_record_invalid_error (line 107) | def handle_record_invalid_error(error)
      method handle_email_error (line 115) | def handle_email_error(error)
      method handle_generic_error (line 123) | def handle_generic_error(error)

FILE: app/services/families/locations.rb
  class Families::Locations (line 3) | class Families::Locations
    method initialize (line 6) | def initialize(user)
    method call (line 12) | def call
    method history (line 22) | def history(start_at:, end_at:)
    method family_feature_enabled? (line 34) | def family_feature_enabled?
    method family_members_with_sharing_enabled (line 38) | def family_members_with_sharing_enabled
    method build_family_locations (line 43) | def build_family_locations(sharing_members)
    method build_family_history (line 62) | def build_family_history(sharing_members, start_at:, end_at:)
    method numbered_rows_sql (line 88) | def numbered_rows_sql(scope)

FILE: app/services/families/memberships/destroy.rb
  type Families (line 3) | module Families
    type Memberships (line 4) | module Memberships
      class Destroy (line 5) | class Destroy
        method initialize (line 8) | def initialize(user:, member_to_remove: nil)
        method call (line 14) | def call
        method validate_can_leave (line 38) | def validate_can_leave
        method validate_in_family (line 45) | def validate_in_family
        method validate_removal_allowed (line 52) | def validate_removal_allowed
        method removing_self? (line 62) | def removing_self?
        method validate_owner_can_leave (line 66) | def validate_owner_can_leave
        method validate_remover_is_owner (line 73) | def validate_remover_is_owner
        method validate_same_family (line 80) | def validate_same_family
        method validate_not_removing_owner (line 87) | def validate_not_removing_owner
        method remove_membership (line 94) | def remove_membership
        method send_notifications (line 98) | def send_notifications
        method send_self_removal_notifications (line 106) | def send_self_removal_notifications
        method send_member_removed_notifications (line 124) | def send_member_removed_notifications
        metho
Condensed preview — 1530 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (8,229K chars).
[
  {
    "path": ".app_version",
    "chars": 6,
    "preview": "1.3.4\n"
  },
  {
    "path": ".circleci/config.yml",
    "chars": 1801,
    "preview": "version: 2.1\n\norbs:\n  ruby: circleci/ruby@2.1.4\n  browser-tools: circleci/browser-tools@1.4.8\n\njobs:\n  test:\n    docker:"
  },
  {
    "path": ".devcontainer/Dockerfile",
    "chars": 941,
    "preview": "# Base-Image for Ruby and Node.js\nFROM ruby:3.4.6-alpine\n\nENV APP_PATH=/var/app\nENV BUNDLE_VERSION=2.5.21\nENV BUNDLE_PAT"
  },
  {
    "path": ".devcontainer/devcontainer.json",
    "chars": 610,
    "preview": "{\n  \"name\": \"Ruby and Node DevContainer\",\n  \"dockerComposeFile\": [\"docker-compose.yml\"],\n  \"service\": \"dawarich_dev\",\n  "
  },
  {
    "path": ".devcontainer/docker-compose.yml",
    "chars": 1810,
    "preview": "networks:\n  dawarich:\nservices:\n  dawarich_dev:\n    build:\n      context: .\n      dockerfile: Dockerfile\n    container_n"
  },
  {
    "path": ".dockerignore",
    "chars": 246,
    "preview": "/log\n/tmp\n\n# We need directories for import and export files, but not the files themselves.\n/public/exports/*\n!/public/e"
  },
  {
    "path": ".gitattributes",
    "chars": 246,
    "preview": "# See https://git-scm.com/docs/gitattributes for more about git attribute files.\n\n# Mark the database schema as having b"
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 840,
    "preview": "# These are supported funding model platforms\n\ngithub: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [u"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 876,
    "preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**BEFORE OPENING"
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 434,
    "preview": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where "
  },
  {
    "path": ".github/workflows/attach_compose.yml",
    "chars": 469,
    "preview": "name: Attach docker-compose.yml to release\n\non:\n  release:\n    types: [published]\n\npermissions:\n  contents: write\n\njobs:"
  },
  {
    "path": ".github/workflows/biome.yml",
    "chars": 1074,
    "preview": "name: biome\non:\n  push:\n  pull_request:\njobs:\n  quality:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: rea"
  },
  {
    "path": ".github/workflows/build_and_push.yml",
    "chars": 6006,
    "preview": "name: Docker image build and push\n\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: \"The branch t"
  },
  {
    "path": ".github/workflows/ci.yml",
    "chars": 2539,
    "preview": "name: CI\n# Not functional at the moment\n\non:\n  pull_request:\n  push:\n    branches: [main]\n\npermissions:\n  contents: read"
  },
  {
    "path": ".github/workflows/release_notifications.yml",
    "chars": 5427,
    "preview": "name: Release Notifications\n\non:\n  workflow_run:\n    workflows: [\"Docker image build and push\"]\n    types: [completed]\n\n"
  },
  {
    "path": ".github/workflows/rubocop.yml",
    "chars": 1350,
    "preview": "name: RuboCop\non:\n  push:\n  pull_request:\n\npermissions:\n  contents: read\n\njobs:\n  rubocop:\n    runs-on: ubuntu-latest\n  "
  },
  {
    "path": ".gitignore",
    "chars": 1744,
    "preview": "# See https://help.github.com/articles/ignoring-files for more about ignoring files.\n#\n# If you find yourself ignoring t"
  },
  {
    "path": ".rspec",
    "chars": 32,
    "preview": "--require spec_helper\n--profile\n"
  },
  {
    "path": ".rubocop.yml",
    "chars": 1242,
    "preview": "AllCops:\n  NewCops: disable\n  Exclude:\n    - 'db/schema.rb'\nplugins: rubocop-rails\n\nStyle/Documentation:\n  Enabled: fals"
  },
  {
    "path": ".ruby-version",
    "chars": 6,
    "preview": "3.4.6\n"
  },
  {
    "path": "AGENTS.md",
    "chars": 2938,
    "preview": "# Repository Guidelines\n\n## Project Structure & Module Organization\nDawarich is a Rails 8 monolith. Controllers, models,"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 148299,
    "preview": "# Changelog\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changel"
  },
  {
    "path": "CLAUDE.md",
    "chars": 23485,
    "preview": "# CLAUDE.md - Dawarich Development Guide\n\nThis file contains essential information for Claude to work effectively with t"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 1786,
    "preview": "## How to contribute to Dawarich\n\n#### **Did you find a bug?**\n\n* **Ensure the bug was not already reported** by searchi"
  },
  {
    "path": "DEVELOPMENT.md",
    "chars": 865,
    "preview": "If you want to develop with dawarich you can use the devcontainer, with your IDE. It is tested with visual studio code.\n"
  },
  {
    "path": "Gemfile",
    "chars": 2409,
    "preview": "# frozen_string_literal: true\n\nsource 'https://rubygems.org'\ngit_source(:github) { |repo| \"https://github.com/#{repo}.gi"
  },
  {
    "path": "LICENSE",
    "chars": 34523,
    "preview": "                    GNU AFFERO GENERAL PUBLIC LICENSE\n                       Version 3, 19 November 2007\n\n Copyright (C)"
  },
  {
    "path": "Procfile",
    "chars": 128,
    "preview": "release: bundle exec rails db:migrate\nweb: bundle exec puma -C config/puma.rb\nworker: bundle exec sidekiq -C config/side"
  },
  {
    "path": "Procfile.dev",
    "chars": 69,
    "preview": "web: bin/rails server -p 3000 -b ::\ncss: bin/rails tailwindcss:watch\n"
  },
  {
    "path": "Procfile.production",
    "chars": 150,
    "preview": "web: bundle exec puma -C config/puma.rb\nworker: bundle exec sidekiq -C config/sidekiq.yml\nprometheus_exporter: bundle ex"
  },
  {
    "path": "Procfile.prometheus.dev",
    "chars": 96,
    "preview": "prometheus_exporter: bundle exec prometheus_exporter -b ANY\nweb: bin/rails server -p 3000 -b ::\n"
  },
  {
    "path": "README.md",
    "chars": 6466,
    "preview": "# 🌍 Dawarich: Your Self-Hostable Location History Tracker\n\n[![Discord](https://dcbadge.limes.pink/api/server/pHsBjpt5J8)"
  },
  {
    "path": "Rakefile",
    "chars": 258,
    "preview": "# frozen_string_literal: true\n\n# Add your own tasks in files placed in lib/tasks ending in .rake,\n# for example lib/task"
  },
  {
    "path": "app/assets/builds/.keep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "app/assets/builds/tailwind.css",
    "chars": 176250,
    "preview": "*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--t"
  },
  {
    "path": "app/assets/config/manifest.js",
    "chars": 224,
    "preview": "//= link rails-ujs.js\n//= link_tree ../images\n//= link_directory ../stylesheets .css\n//= link_tree ../builds\n//= link_tr"
  },
  {
    "path": "app/assets/images/.keep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "app/assets/images/favicon/browserconfig.xml.erb",
    "chars": 273,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<browserconfig>\n    <msapplication>\n        <tile>\n            <square150x150logo"
  },
  {
    "path": "app/assets/stylesheets/actiontext.css",
    "chars": 1440,
    "preview": "/*\n * Provides a drop-in pointer for the default Trix stylesheet that will format the toolbar and\n * the trix-editor con"
  },
  {
    "path": "app/assets/stylesheets/application.css",
    "chars": 3267,
    "preview": "/*\n * This is a manifest file that'll be compiled into application.css, which will include all the files\n * listed below"
  },
  {
    "path": "app/assets/stylesheets/application.tailwind.css",
    "chars": 4847,
    "preview": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n@tailwind daisyui;\n\n/*\n\n@layer components {\n  .btn-primary {\n"
  },
  {
    "path": "app/assets/stylesheets/leaflet.control.layers.tree.css",
    "chars": 642,
    "preview": ".leaflet-control-layers-toggle.leaflet-layerstree-named-toggle {\n  margin: 2px 5px;\n  width: auto;\n  height: auto;\n  bac"
  },
  {
    "path": "app/assets/stylesheets/leaflet_theme.css",
    "chars": 11332,
    "preview": "/* Leaflet Theme Styles - Light and Dark mode support */\n\n/* CSS Custom Properties for Light Theme */\n[data-theme=\"light"
  },
  {
    "path": "app/assets/stylesheets/maplibre-gl.css",
    "chars": 69449,
    "preview": ".maplibregl-map{font:12px/20px Helvetica Neue,Arial,Helvetica,sans-serif;overflow:hidden;position:relative;-webkit-tap-h"
  },
  {
    "path": "app/assets/stylesheets/maps_maplibre.css",
    "chars": 5456,
    "preview": "/* Maps V2 Styles */\n\n/* Loading Overlay */\n.loading-overlay {\n  position: absolute;\n  inset: 0;\n  background: rgba(255,"
  },
  {
    "path": "app/assets/stylesheets/maps_maplibre_panel.css",
    "chars": 4856,
    "preview": "/* Maps V2 Control Panel Styles */\n\n.map-control-panel {\n  position: absolute;\n  top: 0;\n  right: -480px; /* Hidden by d"
  },
  {
    "path": "app/assets/stylesheets/maps_maplibre_replay.css",
    "chars": 8651,
    "preview": "/* Maps V2 Replay Panel Styles */\n\n.replay-panel {\n  position: absolute;\n  bottom: 20px;\n  left: 50%;\n  transform: trans"
  },
  {
    "path": "app/assets/stylesheets/maps_maplibre_timeline_feed.css",
    "chars": 5628,
    "preview": "/* Timeline Feed — Vertical timeline with rail, nodes, and connectors */\n\n/* Loading state: Turbo adds [busy] while fetc"
  },
  {
    "path": "app/channels/application_cable/channel.rb",
    "chars": 110,
    "preview": "# frozen_string_literal: true\n\nmodule ApplicationCable\n  class Channel < ActionCable::Channel::Base\n  end\nend\n"
  },
  {
    "path": "app/channels/application_cable/connection.rb",
    "chars": 395,
    "preview": "# frozen_string_literal: true\n\nmodule ApplicationCable\n  class Connection < ActionCable::Connection::Base\n    identified"
  },
  {
    "path": "app/channels/family_locations_channel.rb",
    "chars": 346,
    "preview": "# frozen_string_literal: true\n\nclass FamilyLocationsChannel < ApplicationCable::Channel\n  def subscribed\n    return reje"
  },
  {
    "path": "app/channels/imports_channel.rb",
    "chars": 135,
    "preview": "# frozen_string_literal: true\n\nclass ImportsChannel < ApplicationCable::Channel\n  def subscribed\n    stream_for current_"
  },
  {
    "path": "app/channels/notifications_channel.rb",
    "chars": 141,
    "preview": "# frozen_string_literal: true\n\nclass NotificationsChannel < ApplicationCable::Channel\n  def subscribed\n    stream_for cu"
  },
  {
    "path": "app/channels/points_channel.rb",
    "chars": 134,
    "preview": "# frozen_string_literal: true\n\nclass PointsChannel < ApplicationCable::Channel\n  def subscribed\n    stream_for current_u"
  },
  {
    "path": "app/channels/tracks_channel.rb",
    "chars": 134,
    "preview": "# frozen_string_literal: true\n\nclass TracksChannel < ApplicationCable::Channel\n  def subscribed\n    stream_for current_u"
  },
  {
    "path": "app/controllers/api/v1/areas_controller.rb",
    "chars": 1026,
    "preview": "# frozen_string_literal: true\n\nclass Api::V1::AreasController < ApiController\n  before_action :set_area, only: %i[show u"
  },
  {
    "path": "app/controllers/api/v1/countries/borders_controller.rb",
    "chars": 307,
    "preview": "# frozen_string_literal: true\n\nclass Api::V1::Countries::BordersController < ApiController\n  def index\n    countries = R"
  },
  {
    "path": "app/controllers/api/v1/countries/visited_cities_controller.rb",
    "chars": 738,
    "preview": "# frozen_string_literal: true\n\nclass Api::V1::Countries::VisitedCitiesController < ApiController\n  include SafeTimestamp"
  },
  {
    "path": "app/controllers/api/v1/digests_controller.rb",
    "chars": 2026,
    "preview": "# frozen_string_literal: true\n\nclass Api::V1::DigestsController < ApiController\n  before_action :authenticate_active_api"
  },
  {
    "path": "app/controllers/api/v1/families/locations_controller.rb",
    "chars": 1302,
    "preview": "# frozen_string_literal: true\n\nclass Api::V1::Families::LocationsController < ApiController\n  before_action :ensure_fami"
  },
  {
    "path": "app/controllers/api/v1/health_controller.rb",
    "chars": 179,
    "preview": "# frozen_string_literal: true\n\nclass Api::V1::HealthController < ApiController\n  skip_before_action :authenticate_api_ke"
  },
  {
    "path": "app/controllers/api/v1/imports_controller.rb",
    "chars": 3235,
    "preview": "# frozen_string_literal: true\n\nclass Api::V1::ImportsController < ApiController\n  ALLOWED_EXTENSIONS = %w[.gpx .geojson "
  },
  {
    "path": "app/controllers/api/v1/insights_controller.rb",
    "chars": 3494,
    "preview": "# frozen_string_literal: true\n\nclass Api::V1::InsightsController < ApiController\n  def index\n    load_year_data\n    load"
  },
  {
    "path": "app/controllers/api/v1/locations_controller.rb",
    "chars": 2726,
    "preview": "# frozen_string_literal: true\n\nclass Api::V1::LocationsController < ApiController\n  before_action :validate_search_param"
  },
  {
    "path": "app/controllers/api/v1/maps/hexagons_controller.rb",
    "chars": 2807,
    "preview": "# frozen_string_literal: true\n\nclass Api::V1::Maps::HexagonsController < ApiController\n  skip_before_action :authenticat"
  },
  {
    "path": "app/controllers/api/v1/overland/batches_controller.rb",
    "chars": 646,
    "preview": "# frozen_string_literal: true\n\nclass Api::V1::Overland::BatchesController < ApiController\n  before_action :authenticate_"
  },
  {
    "path": "app/controllers/api/v1/owntracks/points_controller.rb",
    "chars": 563,
    "preview": "# frozen_string_literal: true\n\nclass Api::V1::Owntracks::PointsController < ApiController\n  before_action :authenticate_"
  },
  {
    "path": "app/controllers/api/v1/photos_controller.rb",
    "chars": 2393,
    "preview": "# frozen_string_literal: true\n\nclass Api::V1::PhotosController < ApiController\n  before_action :check_integration_config"
  },
  {
    "path": "app/controllers/api/v1/places_controller.rb",
    "chars": 5175,
    "preview": "# frozen_string_literal: true\n\nmodule Api\n  module V1\n    class PlacesController < ApiController\n      before_action :se"
  },
  {
    "path": "app/controllers/api/v1/plan_controller.rb",
    "chars": 786,
    "preview": "# frozen_string_literal: true\n\nclass Api::V1::PlanController < ApiController\n  def show\n    features = if DawarichSettin"
  },
  {
    "path": "app/controllers/api/v1/points/tracked_months_controller.rb",
    "chars": 164,
    "preview": "# frozen_string_literal: true\n\nclass Api::V1::Points::TrackedMonthsController < ApiController\n  def index\n    render jso"
  },
  {
    "path": "app/controllers/api/v1/points_controller.rb",
    "chars": 3769,
    "preview": "# frozen_string_literal: true\n\nclass Api::V1::PointsController < ApiController\n  include SafeTimestampParser\n\n  before_a"
  },
  {
    "path": "app/controllers/api/v1/settings_controller.rb",
    "chars": 2782,
    "preview": "# frozen_string_literal: true\n\nclass Api::V1::SettingsController < ApiController\n  before_action :authenticate_active_ap"
  },
  {
    "path": "app/controllers/api/v1/stats_controller.rb",
    "chars": 160,
    "preview": "# frozen_string_literal: true\n\nclass Api::V1::StatsController < ApiController\n  def index\n    render json: StatsSerializ"
  },
  {
    "path": "app/controllers/api/v1/subscriptions_controller.rb",
    "chars": 1206,
    "preview": "# frozen_string_literal: true\n\nclass Api::V1::SubscriptionsController < ApiController\n  skip_before_action :authenticate"
  },
  {
    "path": "app/controllers/api/v1/tags_controller.rb",
    "chars": 287,
    "preview": "# frozen_string_literal: true\n\nmodule Api\n  module V1\n    class TagsController < ApiController\n      def privacy_zones\n "
  },
  {
    "path": "app/controllers/api/v1/timeline_controller.rb",
    "chars": 1178,
    "preview": "# frozen_string_literal: true\n\nmodule Api\n  module V1\n    class TimelineController < ApiController\n      MAX_RANGE_DAYS "
  },
  {
    "path": "app/controllers/api/v1/tracks/points_controller.rb",
    "chars": 1259,
    "preview": "# frozen_string_literal: true\n\nclass Api::V1::Tracks::PointsController < ApiController\n  def index\n    track = current_a"
  },
  {
    "path": "app/controllers/api/v1/tracks_controller.rb",
    "chars": 650,
    "preview": "# frozen_string_literal: true\n\nclass Api::V1::TracksController < ApiController\n  def index\n    tracks_query = Tracks::In"
  },
  {
    "path": "app/controllers/api/v1/users_controller.rb",
    "chars": 161,
    "preview": "# frozen_string_literal: true\n\nclass Api::V1::UsersController < ApiController\n  def me\n    render json: Api::UserSeriali"
  },
  {
    "path": "app/controllers/api/v1/visits/possible_places_controller.rb",
    "chars": 420,
    "preview": "# frozen_string_literal: true\n\nclass Api::V1::Visits::PossiblePlacesController < ApiController\n  def index\n    visit = c"
  },
  {
    "path": "app/controllers/api/v1/visits_controller.rb",
    "chars": 3673,
    "preview": "# frozen_string_literal: true\n\nclass Api::V1::VisitsController < ApiController\n  def index\n    visits = Visits::Finder.n"
  },
  {
    "path": "app/controllers/api_controller.rb",
    "chars": 4105,
    "preview": "# frozen_string_literal: true\n\nclass ApiController < ApplicationController\n  skip_before_action :verify_authenticity_tok"
  },
  {
    "path": "app/controllers/application_controller.rb",
    "chars": 3831,
    "preview": "# frozen_string_literal: true\n\nclass ApplicationController < ActionController::Base\n  include Pundit::Authorization\n\n  r"
  },
  {
    "path": "app/controllers/areas_controller.rb",
    "chars": 682,
    "preview": "# frozen_string_literal: true\n\nclass AreasController < ApplicationController\n  include FlashStreamable\n\n  before_action "
  },
  {
    "path": "app/controllers/auth/ios_controller.rb",
    "chars": 672,
    "preview": "# frozen_string_literal: true\n\nmodule Auth\n  class IosController < ApplicationController\n    def success\n      # If toke"
  },
  {
    "path": "app/controllers/concerns/.keep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "app/controllers/concerns/flash_streamable.rb",
    "chars": 259,
    "preview": "# frozen_string_literal: true\n\nmodule FlashStreamable\n  extend ActiveSupport::Concern\n\n  private\n\n  def stream_flash(typ"
  },
  {
    "path": "app/controllers/concerns/safe_timestamp_parser.rb",
    "chars": 893,
    "preview": "# frozen_string_literal: true\n\nmodule SafeTimestampParser\n  extend ActiveSupport::Concern\n\n  private\n\n  def safe_timesta"
  },
  {
    "path": "app/controllers/concerns/sortable.rb",
    "chars": 546,
    "preview": "# frozen_string_literal: true\n\nmodule Sortable\n  extend ActiveSupport::Concern\n\n  private\n\n  def sorted(scope)\n    if so"
  },
  {
    "path": "app/controllers/concerns/utm_trackable.rb",
    "chars": 740,
    "preview": "# frozen_string_literal: true\n\nmodule UtmTrackable\n  extend ActiveSupport::Concern\n\n  UTM_PARAMS = %w[utm_source utm_med"
  },
  {
    "path": "app/controllers/exports_controller.rb",
    "chars": 1275,
    "preview": "# frozen_string_literal: true\n\nclass ExportsController < ApplicationController\n  include ActiveStorage::SetCurrent\n  inc"
  },
  {
    "path": "app/controllers/families_controller.rb",
    "chars": 2315,
    "preview": "# frozen_string_literal: true\n\nclass FamiliesController < ApplicationController\n  before_action :authenticate_user!\n  be"
  },
  {
    "path": "app/controllers/family/invitations_controller.rb",
    "chars": 2203,
    "preview": "# frozen_string_literal: true\n\nclass Family::InvitationsController < ApplicationController\n  before_action :authenticate"
  },
  {
    "path": "app/controllers/family/location_requests_controller.rb",
    "chars": 2195,
    "preview": "# frozen_string_literal: true\n\nclass Family::LocationRequestsController < ApplicationController\n  before_action :authent"
  },
  {
    "path": "app/controllers/family/location_sharing_controller.rb",
    "chars": 1579,
    "preview": "# frozen_string_literal: true\n\nclass Family::LocationSharingController < ApplicationController\n  include FlashStreamable"
  },
  {
    "path": "app/controllers/family/memberships_controller.rb",
    "chars": 2290,
    "preview": "# frozen_string_literal: true\n\nclass Family::MembershipsController < ApplicationController\n  before_action :authenticate"
  },
  {
    "path": "app/controllers/home_controller.rb",
    "chars": 342,
    "preview": "# frozen_string_literal: true\n\nclass HomeController < ApplicationController\n  include ApplicationHelper\n\n  def index\n   "
  },
  {
    "path": "app/controllers/imports_controller.rb",
    "chars": 4177,
    "preview": "# frozen_string_literal: true\n\nclass ImportsController < ApplicationController\n  include ActiveStorage::SetCurrent\n  inc"
  },
  {
    "path": "app/controllers/insights_controller.rb",
    "chars": 7410,
    "preview": "# frozen_string_literal: true\n\nclass InsightsController < ApplicationController\n  before_action :authenticate_user!\n\n  d"
  },
  {
    "path": "app/controllers/map/leaflet_controller.rb",
    "chars": 3086,
    "preview": "# frozen_string_literal: true\n\nclass Map::LeafletController < ApplicationController\n  include SafeTimestampParser\n\n  bef"
  },
  {
    "path": "app/controllers/map/maplibre_controller.rb",
    "chars": 695,
    "preview": "# frozen_string_literal: true\n\nmodule Map\n  class MaplibreController < ApplicationController\n    include SafeTimestampPa"
  },
  {
    "path": "app/controllers/map/timeline_feeds_controller.rb",
    "chars": 834,
    "preview": "# frozen_string_literal: true\n\nmodule Map\n  class TimelineFeedsController < ApplicationController\n    include SafeTimest"
  },
  {
    "path": "app/controllers/metrics_controller.rb",
    "chars": 460,
    "preview": "# frozen_string_literal: true\n\nclass MetricsController < ApplicationController\n  http_basic_authenticate_with name: METR"
  },
  {
    "path": "app/controllers/notifications_controller.rb",
    "chars": 1046,
    "preview": "# frozen_string_literal: true\n\nclass NotificationsController < ApplicationController\n  before_action :authenticate_user!"
  },
  {
    "path": "app/controllers/places_controller.rb",
    "chars": 3401,
    "preview": "# frozen_string_literal: true\n\nclass PlacesController < ApplicationController\n  include FlashStreamable\n\n  before_action"
  },
  {
    "path": "app/controllers/points_controller.rb",
    "chars": 1694,
    "preview": "# frozen_string_literal: true\n\nclass PointsController < ApplicationController\n  include SafeTimestampParser\n\n  before_ac"
  },
  {
    "path": "app/controllers/settings/background_jobs_controller.rb",
    "chars": 1370,
    "preview": "# frozen_string_literal: true\n\nclass Settings::BackgroundJobsController < ApplicationController\n  before_action :authent"
  },
  {
    "path": "app/controllers/settings/general_controller.rb",
    "chars": 2180,
    "preview": "# frozen_string_literal: true\n\nclass Settings::GeneralController < ApplicationController\n  before_action :authenticate_u"
  },
  {
    "path": "app/controllers/settings/integrations_controller.rb",
    "chars": 930,
    "preview": "# frozen_string_literal: true\n\nclass Settings::IntegrationsController < ApplicationController\n  before_action :authentic"
  },
  {
    "path": "app/controllers/settings/maps_controller.rb",
    "chars": 470,
    "preview": "# frozen_string_literal: true\n\nclass Settings::MapsController < ApplicationController\n  before_action :authenticate_user"
  },
  {
    "path": "app/controllers/settings/onboardings_controller.rb",
    "chars": 272,
    "preview": "# frozen_string_literal: true\n\nmodule Settings\n  class OnboardingsController < ApplicationController\n    before_action :"
  },
  {
    "path": "app/controllers/settings/users_controller.rb",
    "chars": 5970,
    "preview": "# frozen_string_literal: true\n\nclass Settings::UsersController < ApplicationController\n  before_action :authenticate_sel"
  },
  {
    "path": "app/controllers/settings_controller.rb",
    "chars": 365,
    "preview": "# frozen_string_literal: true\n\nclass SettingsController < ApplicationController\n  before_action :authenticate_user!\n\n  d"
  },
  {
    "path": "app/controllers/shared/digests_controller.rb",
    "chars": 1519,
    "preview": "# frozen_string_literal: true\n\nclass Shared::DigestsController < ApplicationController\n  helper Users::DigestsHelper\n  h"
  },
  {
    "path": "app/controllers/shared/stats_controller.rb",
    "chars": 2075,
    "preview": "# frozen_string_literal: true\n\nclass Shared::StatsController < ApplicationController\n  include FlashStreamable\n\n  before"
  },
  {
    "path": "app/controllers/stats_controller.rb",
    "chars": 2989,
    "preview": "# frozen_string_literal: true\n\nclass StatsController < ApplicationController\n  before_action :authenticate_user!\n  befor"
  },
  {
    "path": "app/controllers/tags_controller.rb",
    "chars": 1132,
    "preview": "# frozen_string_literal: true\n\nclass TagsController < ApplicationController\n  before_action :authenticate_user!\n  before"
  },
  {
    "path": "app/controllers/trips_controller.rb",
    "chars": 1800,
    "preview": "# frozen_string_literal: true\n\nclass TripsController < ApplicationController\n  before_action :authenticate_user!\n  befor"
  },
  {
    "path": "app/controllers/users/digests_controller.rb",
    "chars": 1791,
    "preview": "# frozen_string_literal: true\n\nclass Users::DigestsController < ApplicationController\n  helper Users::DigestsHelper\n  he"
  },
  {
    "path": "app/controllers/users/omniauth_callbacks_controller.rb",
    "chars": 2196,
    "preview": "# frozen_string_literal: true\n\nclass Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController\n  def gith"
  },
  {
    "path": "app/controllers/users/registrations_controller.rb",
    "chars": 3851,
    "preview": "# frozen_string_literal: true\n\nclass Users::RegistrationsController < Devise::RegistrationsController\n  include UtmTrack"
  },
  {
    "path": "app/controllers/users/sessions_controller.rb",
    "chars": 916,
    "preview": "# frozen_string_literal: true\n\nclass Users::SessionsController < Devise::SessionsController\n  before_action :load_invita"
  },
  {
    "path": "app/controllers/visits_controller.rb",
    "chars": 2613,
    "preview": "# frozen_string_literal: true\n\nclass VisitsController < ApplicationController\n  include FlashStreamable\n\n  before_action"
  },
  {
    "path": "app/helpers/application_helper.rb",
    "chars": 5319,
    "preview": "# frozen_string_literal: true\n\nmodule ApplicationHelper\n  def show_plan_data_window_alert?\n    !DawarichSettings.self_ho"
  },
  {
    "path": "app/helpers/country_flag_helper.rb",
    "chars": 773,
    "preview": "# frozen_string_literal: true\n\nmodule CountryFlagHelper\n  def country_flag(country_name)\n    country_code = country_to_c"
  },
  {
    "path": "app/helpers/datetime_formatting_helper.rb",
    "chars": 1239,
    "preview": "# frozen_string_literal: true\n\nmodule DatetimeFormattingHelper\n  def human_date(date)\n    date.strftime('%e %B %Y')\n  en"
  },
  {
    "path": "app/helpers/flash_helper.rb",
    "chars": 497,
    "preview": "# frozen_string_literal: true\n\nmodule FlashHelper\n  def flash_alert_class(type)\n    case type.to_sym\n    when :notice, :"
  },
  {
    "path": "app/helpers/insights_helper.rb",
    "chars": 7683,
    "preview": "# frozen_string_literal: true\n\nmodule InsightsHelper\n  include CountryFlagHelper\n\n  def monthly_digest_title(digest)\n   "
  },
  {
    "path": "app/helpers/month_styling_helper.rb",
    "chars": 2347,
    "preview": "# frozen_string_literal: true\n\nmodule MonthStylingHelper\n  MONTH_ICONS = {\n    (1..2) => 'snowflake', (3..5) => 'flower'"
  },
  {
    "path": "app/helpers/points_helper.rb",
    "chars": 318,
    "preview": "# frozen_string_literal: true\n\nmodule PointsHelper\n  def link_to_date(timestamp)\n    datetime = Time.zone.at(timestamp)\n"
  },
  {
    "path": "app/helpers/stats_comparison_helper.rb",
    "chars": 1570,
    "preview": "# frozen_string_literal: true\n\nmodule StatsComparisonHelper\n  def x_than_average_distance(stat, average_distance_this_ye"
  },
  {
    "path": "app/helpers/stats_helper.rb",
    "chars": 4276,
    "preview": "# frozen_string_literal: true\n\nmodule StatsHelper\n  def year_distance_stat(year_data, user)\n    Stat.convert_distance(ye"
  },
  {
    "path": "app/helpers/tags_helper.rb",
    "chars": 401,
    "preview": "# frozen_string_literal: true\n\nmodule TagsHelper\n  COMMON_TAG_EMOJIS = %w[\n    🏠 🏢 🏫 🏥 🏪 🏨 🏦 🏛️ 🏟️ 🏖️\n    ⛪ 🕌 🕍 ⛩️ 🗼 🗽 🗿"
  },
  {
    "path": "app/helpers/trips_helper.rb",
    "chars": 1825,
    "preview": "# frozen_string_literal: true\n\nmodule TripsHelper\n  def immich_search_url(base_url, start_date, end_date)\n    query = {\n"
  },
  {
    "path": "app/helpers/user_helper.rb",
    "chars": 422,
    "preview": "# frozen_string_literal: true\n\nmodule UserHelper\n  def api_key_qr_code(user, size: 6)\n    json = { 'server_url' => root_"
  },
  {
    "path": "app/helpers/users/digests_helper.rb",
    "chars": 1910,
    "preview": "# frozen_string_literal: true\n\nmodule Users\n  module DigestsHelper\n    PROGRESS_COLORS = %w[\n      progress-primary prog"
  },
  {
    "path": "app/javascript/README.md",
    "chars": 17341,
    "preview": "# Dawarich JavaScript Architecture\n\nThis document provides a comprehensive guide to the JavaScript architecture used in "
  },
  {
    "path": "app/javascript/application.js",
    "chars": 242,
    "preview": "// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails\n\nimport \"@rails"
  },
  {
    "path": "app/javascript/channels/consumer.js",
    "chars": 270,
    "preview": "// Action Cable provides the framework to deal with WebSockets in Rails.\n// You can generate new channels where WebSocke"
  },
  {
    "path": "app/javascript/channels/family_locations_channel.js",
    "chars": 767,
    "preview": "import consumer from \"./consumer\"\n\n// Only create subscription if family feature is enabled\nconst familyFeaturesElement "
  },
  {
    "path": "app/javascript/channels/imports_channel.js",
    "chars": 167,
    "preview": "// Imports channel subscriptions are handled by:\n// - controllers/imports_controller.js\n// This file is kept for referen"
  },
  {
    "path": "app/javascript/channels/index.js",
    "chars": 320,
    "preview": "// Import all the channels to be used by Action Cable\n// Note: Most channel subscriptions are created by their respectiv"
  },
  {
    "path": "app/javascript/channels/notifications_channel.js",
    "chars": 269,
    "preview": "// Notifications channel subscriptions are handled by:\n// - controllers/notifications_controller.js (for navbar notifica"
  },
  {
    "path": "app/javascript/channels/points_channel.js",
    "chars": 245,
    "preview": "// Points channel subscriptions are handled by:\n// - controllers/maps_controller.js (for Leaflet maps)\n// - maps_maplibr"
  },
  {
    "path": "app/javascript/controllers/activity_heatmap_controller.js",
    "chars": 2347,
    "preview": "import { Controller } from \"@hotwired/stimulus\"\n\nexport default class extends Controller {\n  static targets = [\"tooltip\""
  },
  {
    "path": "app/javascript/controllers/add_visit_controller.js",
    "chars": 14076,
    "preview": "import { Controller } from \"@hotwired/stimulus\"\nimport L from \"leaflet\"\nimport {\n  setAddVisitButtonActive,\n  setAddVisi"
  },
  {
    "path": "app/javascript/controllers/application.js",
    "chars": 216,
    "preview": "import { Application } from \"@hotwired/stimulus\"\n\nconst application = Application.start()\n\n// Configure Stimulus develop"
  },
  {
    "path": "app/javascript/controllers/area_creation_v2_controller.js",
    "chars": 1083,
    "preview": "import { Controller } from \"@hotwired/stimulus\"\n\nexport default class extends Controller {\n  static targets = [\n    \"mod"
  },
  {
    "path": "app/javascript/controllers/area_drawer_controller.js",
    "chars": 3286,
    "preview": "import { Controller } from \"@hotwired/stimulus\"\nimport { calculateDistance, createCircle } from \"maps_maplibre/utils/geo"
  },
  {
    "path": "app/javascript/controllers/area_selector_controller.js",
    "chars": 3874,
    "preview": "import { Controller } from \"@hotwired/stimulus\"\nimport { createRectangle } from \"maps_maplibre/utils/geometry\"\n\n/**\n * A"
  },
  {
    "path": "app/javascript/controllers/base_controller.js",
    "chars": 625,
    "preview": "import { Controller } from \"@hotwired/stimulus\"\n\nexport default class extends Controller {\n  static values = {\n    selfH"
  },
  {
    "path": "app/javascript/controllers/checkbox_select_all_controller.js",
    "chars": 1141,
    "preview": "import BaseController from \"./base_controller\"\n\n// Connects to data-controller=\"checkbox-select-all\"\nexport default clas"
  },
  {
    "path": "app/javascript/controllers/clipboard_controller.js",
    "chars": 1257,
    "preview": "import { Controller } from \"@hotwired/stimulus\"\nimport Flash from \"./flash_controller\"\n\nexport default class extends Con"
  },
  {
    "path": "app/javascript/controllers/color_picker_controller.js",
    "chars": 2174,
    "preview": "import { Controller } from \"@hotwired/stimulus\"\n\n// Enhanced Color Picker Controller\n// Based on RailsBlocks pattern: ht"
  },
  {
    "path": "app/javascript/controllers/datetime_controller.js",
    "chars": 3907,
    "preview": "// This controller is being used on:\n// - trips/new\n// - trips/edit\n\nimport BaseController from \"./base_controller\"\n\nexp"
  },
  {
    "path": "app/javascript/controllers/emoji_picker_controller.js",
    "chars": 4520,
    "preview": "import { Controller } from \"@hotwired/stimulus\"\nimport { Picker } from \"emoji-mart\"\n\n// Emoji Picker Controller\n// Based"
  },
  {
    "path": "app/javascript/controllers/family_members_controller.js",
    "chars": 20907,
    "preview": "import { Controller } from \"@hotwired/stimulus\"\nimport L from \"leaflet\"\nimport Flash from \"./flash_controller\"\n\nexport d"
  },
  {
    "path": "app/javascript/controllers/family_navbar_indicator_controller.js",
    "chars": 1675,
    "preview": "import { Controller } from \"@hotwired/stimulus\"\n\nexport default class extends Controller {\n  static targets = [\"indicato"
  },
  {
    "path": "app/javascript/controllers/flash_controller.js",
    "chars": 2298,
    "preview": "import { Controller } from \"@hotwired/stimulus\"\n\nconst ALERT_CLASSES = {\n  error: \"alert-error\",\n  alert: \"alert-error\","
  },
  {
    "path": "app/javascript/controllers/imports_controller.js",
    "chars": 251,
    "preview": "import BaseController from \"./base_controller\"\n\n// Import progress is now handled via Turbo Stream broadcasts.\n// This c"
  },
  {
    "path": "app/javascript/controllers/index.js",
    "chars": 272,
    "preview": "// Lazy load controllers — only fetched when their data-controller attribute appears in the DOM\nimport { lazyLoadControl"
  },
  {
    "path": "app/javascript/controllers/location_sharing_toggle_controller.js",
    "chars": 4039,
    "preview": "import { Controller } from \"@hotwired/stimulus\"\nimport Flash from \"./flash_controller\"\n\nexport default class extends Con"
  },
  {
    "path": "app/javascript/controllers/map_controls_controller.js",
    "chars": 1316,
    "preview": "import { Controller } from \"@hotwired/stimulus\"\n\nexport default class extends Controller {\n  static targets = [\"panel\", "
  },
  {
    "path": "app/javascript/controllers/map_panel_controller.js",
    "chars": 1720,
    "preview": "import { Controller } from \"@hotwired/stimulus\"\n\n/**\n * Map Panel Controller\n * Handles tab switching in the map control"
  },
  {
    "path": "app/javascript/controllers/map_preview_controller.js",
    "chars": 2069,
    "preview": "import L from \"leaflet\"\nimport BaseController from \"./base_controller\"\nimport Flash from \"./flash_controller\"\n\nexport de"
  },
  {
    "path": "app/javascript/controllers/maps/maplibre/area_selection_manager.js",
    "chars": 18409,
    "preview": "import { Toast } from \"maps_maplibre/components/toast\"\nimport { VisitCard } from \"maps_maplibre/components/visit_card\"\ni"
  },
  {
    "path": "app/javascript/controllers/maps/maplibre/data_loader.js",
    "chars": 15059,
    "preview": "import { RoutesLayer } from \"maps_maplibre/layers/routes_layer\"\nimport { pointsToGeoJSON } from \"maps_maplibre/utils/geo"
  },
  {
    "path": "app/javascript/controllers/maps/maplibre/date_manager.js",
    "chars": 1334,
    "preview": "/**\n * Manages date formatting and range calculations\n */\nexport class DateManager {\n  /**\n   * Format date for API requ"
  },
  {
    "path": "app/javascript/controllers/maps/maplibre/event_handlers.js",
    "chars": 36067,
    "preview": "import maplibregl from \"maplibre-gl\"\nimport {\n  formatDistance,\n  formatSpeed,\n  minutesToDaysHoursMinutes,\n} from \"maps"
  },
  {
    "path": "app/javascript/controllers/maps/maplibre/filter_manager.js",
    "chars": 1232,
    "preview": "/**\n * Manages filtering and searching of map data\n */\nexport class FilterManager {\n  constructor(dataLoader) {\n    this"
  },
  {
    "path": "app/javascript/controllers/maps/maplibre/layer_manager.js",
    "chars": 13533,
    "preview": "import { AreasLayer } from \"maps_maplibre/layers/areas_layer\"\nimport { FamilyLayer } from \"maps_maplibre/layers/family_l"
  },
  {
    "path": "app/javascript/controllers/maps/maplibre/map_data_manager.js",
    "chars": 11996,
    "preview": "import maplibregl from \"maplibre-gl\"\nimport { Toast } from \"maps_maplibre/components/toast\"\nimport { UpgradeBanner } fro"
  },
  {
    "path": "app/javascript/controllers/maps/maplibre/map_initializer.js",
    "chars": 2322,
    "preview": "import maplibregl from \"maplibre-gl\"\nimport { getMapStyle } from \"maps_maplibre/utils/style_manager\"\n\n/**\n * Handles map"
  },
  {
    "path": "app/javascript/controllers/maps/maplibre/places_manager.js",
    "chars": 8582,
    "preview": "import { Toast } from \"maps_maplibre/components/toast\"\nimport { SettingsManager } from \"maps_maplibre/utils/settings_man"
  },
  {
    "path": "app/javascript/controllers/maps/maplibre/routes_manager.js",
    "chars": 16466,
    "preview": "import { Toast } from \"maps_maplibre/components/toast\"\nimport { gatedToggle } from \"maps_maplibre/utils/layer_gate\"\nimpo"
  },
  {
    "path": "app/javascript/controllers/maps/maplibre/settings_manager.js",
    "chars": 40700,
    "preview": "import { Toast } from \"maps_maplibre/components/toast\"\nimport { UpgradeBanner } from \"maps_maplibre/components/upgrade_b"
  },
  {
    "path": "app/javascript/controllers/maps/maplibre/visits_manager.js",
    "chars": 4996,
    "preview": "import { Toast } from \"maps_maplibre/components/toast\"\nimport { SettingsManager } from \"maps_maplibre/utils/settings_man"
  },
  {
    "path": "app/javascript/controllers/maps/maplibre_controller.js",
    "chars": 75616,
    "preview": "import { Controller } from \"@hotwired/stimulus\"\nimport { Toast } from \"maps_maplibre/components/toast\"\nimport { ReplayMa"
  },
  {
    "path": "app/javascript/controllers/maps/maplibre_realtime_controller.js",
    "chars": 7551,
    "preview": "import { Controller } from \"@hotwired/stimulus\"\nimport { createMapChannel } from \"maps_maplibre/channels/map_channel\"\nim"
  },
  {
    "path": "app/javascript/controllers/maps_controller.js",
    "chars": 93237,
    "preview": "import L from \"leaflet\"\nimport \"leaflet.heat\"\nimport \"leaflet.control.layers.tree\"\nimport consumer from \"../channels/con"
  },
  {
    "path": "app/javascript/controllers/notifications_controller.js",
    "chars": 782,
    "preview": "import BaseController from \"./base_controller\"\n\nexport default class extends BaseController {\n  connect() {\n    document"
  },
  {
    "path": "app/javascript/controllers/onboarding_modal_controller.js",
    "chars": 2321,
    "preview": "import { Controller } from \"@hotwired/stimulus\"\n\nexport default class extends Controller {\n  static targets = [\"modal\", "
  },
  {
    "path": "app/javascript/controllers/place_creation_controller.js",
    "chars": 5075,
    "preview": "import { Controller } from \"@hotwired/stimulus\"\n\nexport default class extends Controller {\n  static targets = [\n    \"mod"
  },
  {
    "path": "app/javascript/controllers/places_filter_controller.js",
    "chars": 1393,
    "preview": "import { Controller } from \"@hotwired/stimulus\"\n\nexport default class extends Controller {\n  connect() {\n    console.log"
  },
  {
    "path": "app/javascript/controllers/privacy_radius_controller.js",
    "chars": 910,
    "preview": "import { Controller } from \"@hotwired/stimulus\"\n\nexport default class extends Controller {\n  static targets = [\"toggle\","
  },
  {
    "path": "app/javascript/controllers/public_stat_map_controller.js",
    "chars": 10432,
    "preview": "import L from \"leaflet\"\nimport { createAllMapLayers } from \"../maps/layers\"\nimport BaseController from \"./base_controlle"
  },
  {
    "path": "app/javascript/controllers/removals_controller.js",
    "chars": 593,
    "preview": "import BaseController from \"./base_controller\"\n\nexport default class extends BaseController {\n  static values = {\n    ti"
  },
  {
    "path": "app/javascript/controllers/sharing_modal_controller.js",
    "chars": 1311,
    "preview": "import { Controller } from \"@hotwired/stimulus\"\n\nexport default class extends Controller {\n  static targets = [\n    \"ena"
  },
  {
    "path": "app/javascript/controllers/speed_color_editor_controller.js",
    "chars": 5414,
    "preview": "import { Controller } from \"@hotwired/stimulus\"\n\n/**\n * Speed Color Editor Controller\n * Manages the gradient editor mod"
  },
  {
    "path": "app/javascript/controllers/stat_page_controller.js",
    "chars": 9261,
    "preview": "import L from \"leaflet\"\nimport \"leaflet.heat\"\nimport { createAllMapLayers } from \"../maps/layers\"\nimport BaseController "
  },
  {
    "path": "app/javascript/controllers/timeline_feed_controller.js",
    "chars": 3678,
    "preview": "import { Controller } from \"@hotwired/stimulus\"\n\n/**\n * Timeline Feed Controller\n * Handles accordion single-expand, map"
  },
  {
    "path": "app/javascript/controllers/trip_map_controller.js",
    "chars": 3908,
    "preview": "// This controller is being used on:\n// - trips/index\n\nimport L from \"leaflet\"\nimport { createAllMapLayers } from \"../ma"
  },
  {
    "path": "app/javascript/controllers/trips_controller.js",
    "chars": 8009,
    "preview": "// This controller is being used on:\n// - trips/show\n// - trips/edit\n// - trips/new\n\nimport L from \"leaflet\"\nimport { cr"
  },
  {
    "path": "app/javascript/controllers/upload_controller.js",
    "chars": 6645,
    "preview": "import { Controller } from \"@hotwired/stimulus\"\nimport { DirectUpload } from \"@rails/activestorage\"\nimport Flash from \"."
  },
  {
    "path": "app/javascript/controllers/visit_creation_v2_controller.js",
    "chars": 6883,
    "preview": "import { Controller } from \"@hotwired/stimulus\"\nimport { Toast } from \"maps_maplibre/components/toast\"\n\n/**\n * Controlle"
  },
  {
    "path": "app/javascript/controllers/visit_modal_map_controller.js",
    "chars": 1117,
    "preview": "import L from \"leaflet\"\nimport { osmMapLayer } from \"../maps/layers\"\nimport BaseController from \"./base_controller\"\n\n// "
  },
  {
    "path": "app/javascript/controllers/visit_modal_places_controller.js",
    "chars": 183,
    "preview": "import BaseController from \"./base_controller\"\n\nexport default class extends BaseController {\n  static targets = [\"form\""
  },
  {
    "path": "app/javascript/controllers/visit_name_controller.js",
    "chars": 613,
    "preview": "import BaseController from \"./base_controller\"\n\nexport default class extends BaseController {\n  static targets = [\"name\""
  },
  {
    "path": "app/javascript/controllers/visits_map_controller.js",
    "chars": 2937,
    "preview": "import L from \"leaflet\"\nimport { osmMapLayer } from \"../maps/layers\"\nimport BaseController from \"./base_controller\"\n\nexp"
  }
]

// ... and 1330 more files (download for full content)

About this extraction

This page contains the full source code of the Freika/dawarich GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 1530 files (21.4 MB), approximately 2.0M tokens, and a symbol index with 8919 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!