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<> $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 `