From 3fe48f66853abbc09cf172c311a1a43d7e65bea8 Mon Sep 17 00:00:00 2001 From: Aleksei Besogonov Date: Wed, 8 Jan 2025 19:32:29 -0800 Subject: [PATCH 01/18] Add support for Nominatim This adds support for Nominatim-based reverse geocoding, along with Photon and Geofi. To use it, set the environment variables: NOMINATIM_API_HOST - the host name of the OSM Nominatim server NOMINATIM_API_USE_HTTPS - use the HTTPS to connect NOMINATIM_API_KEY - the API key --- config/initializers/01_constants.rb | 4 ++++ config/initializers/03_dawarich_settings.rb | 6 +++++- config/initializers/geocoder.rb | 6 ++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/config/initializers/01_constants.rb b/config/initializers/01_constants.rb index ce760238..bd5cbca2 100644 --- a/config/initializers/01_constants.rb +++ b/config/initializers/01_constants.rb @@ -13,5 +13,9 @@ PHOTON_API_HOST = ENV.fetch('PHOTON_API_HOST', nil) PHOTON_API_KEY = ENV.fetch('PHOTON_API_KEY', nil) PHOTON_API_USE_HTTPS = ENV.fetch('PHOTON_API_USE_HTTPS', 'true') == 'true' +NOMINATIM_API_HOST = ENV.fetch('NOMINATIM_API_HOST', nil) +NOMINATIM_API_KEY = ENV.fetch('NOMINATIM_API_KEY', nil) +NOMINATIM_API_USE_HTTPS = ENV.fetch('NOMINATIM_API_USE_HTTPS', 'true') == 'true' + GEOAPIFY_API_KEY = ENV.fetch('GEOAPIFY_API_KEY', nil) # /Reverse geocoding settings diff --git a/config/initializers/03_dawarich_settings.rb b/config/initializers/03_dawarich_settings.rb index 87cf4817..34a0d831 100644 --- a/config/initializers/03_dawarich_settings.rb +++ b/config/initializers/03_dawarich_settings.rb @@ -3,7 +3,7 @@ class DawarichSettings class << self def reverse_geocoding_enabled? - @reverse_geocoding_enabled ||= photon_enabled? || geoapify_enabled? + @reverse_geocoding_enabled ||= photon_enabled? || geoapify_enabled? || nominatim_enabled? end def photon_enabled? @@ -17,5 +17,9 @@ class DawarichSettings def geoapify_enabled? @geoapify_enabled ||= GEOAPIFY_API_KEY.present? end + + def nominatim_enabled? + @nominatim_enabled ||= NOMINATIM_API_HOST.present? + end end end diff --git a/config/initializers/geocoder.rb b/config/initializers/geocoder.rb index eb1a7fc4..46cd433d 100644 --- a/config/initializers/geocoder.rb +++ b/config/initializers/geocoder.rb @@ -19,6 +19,12 @@ if PHOTON_API_HOST.present? elsif GEOAPIFY_API_KEY.present? settings[:lookup] = :geoapify settings[:api_key] = GEOAPIFY_API_KEY +elsif NOMINATIM_API_HOST.present? + settings[:lookup] = :nominatim + settings[:nominatim] = { use_https: NOMINATIM_API_USE_HTTPS, host: NOMINATIM_API_HOST } + if NOMINATIM_API_KEY.present? + settings[:api_key] = NOMINATIM_API_KEY + end end Geocoder.configure(settings) From 0b362168c98aca2f1bd6265fb0cffad6fdd476c8 Mon Sep 17 00:00:00 2001 From: Eugene Burmakin Date: Mon, 10 Feb 2025 20:37:20 +0100 Subject: [PATCH 02/18] Implement custom map tiles and user settings with default values --- CHANGELOG.md | 15 ++- app/controllers/settings/maps_controller.rb | 22 +++++ .../controllers/map_preview_controller.js | 67 ++++++++++++++ app/javascript/controllers/maps_controller.js | 30 +++++- app/models/user.rb | 10 +- app/services/areas/visits/create.rb | 4 +- app/services/immich/request_photos.rb | 6 +- app/services/photoprism/request_photos.rb | 6 +- app/services/photos/thumbnail.rb | 14 ++- app/services/users/safe_settings.rb | 91 +++++++++++++++++++ app/views/imports/index.html.erb | 4 +- app/views/map/index.html.erb | 2 +- app/views/settings/_navigation.html.erb | 1 + app/views/settings/index.html.erb | 8 +- app/views/settings/maps/index.html.erb | 45 +++++++++ config/routes.rb | 2 + spec/requests/settings/maps_spec.rb | 43 +++++++++ spec/services/photos/thumbnail_spec.rb | 2 +- 18 files changed, 347 insertions(+), 25 deletions(-) create mode 100644 app/controllers/settings/maps_controller.rb create mode 100644 app/javascript/controllers/map_preview_controller.js create mode 100644 app/services/users/safe_settings.rb create mode 100644 app/views/settings/maps/index.html.erb create mode 100644 spec/requests/settings/maps_spec.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 372d12b5..30ba22d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,20 @@ 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/). -# 0.24.0 - 2025-02-09 +# 0.24.1 - 2025-02-10 + +## Custom map tiles + +In the user settings, you can now set a custom tile URL for the map. This is useful if you want to use a custom map tile provider or if you want to use a map tile provider that is not listed in the dropdown. + +To set a custom tile URL, go to the user settings and set the `Maps` section to your liking. Be mindful that currently, only raster tiles are supported. The URL should be a valid tile URL, like `https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png`. You, as the user, are responsible for any extra costs that may occur due to using a custom tile URL. + +### Added + +- Safe settings for user with default values. +- In the user settings, you can now set a custom tile URL for the map. #429 #715 + +# 0.24.0 - 2025-02-10 ## Points speed units diff --git a/app/controllers/settings/maps_controller.rb b/app/controllers/settings/maps_controller.rb new file mode 100644 index 00000000..59beb04d --- /dev/null +++ b/app/controllers/settings/maps_controller.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class Settings::MapsController < ApplicationController + before_action :authenticate_user! + + def index + @maps = current_user.safe_settings.maps + end + + def update + current_user.settings['maps'] = settings_params + current_user.save! + + redirect_to settings_maps_path, notice: 'Settings updated' + end + + private + + def settings_params + params.require(:maps).permit(:name, :url) + end +end diff --git a/app/javascript/controllers/map_preview_controller.js b/app/javascript/controllers/map_preview_controller.js new file mode 100644 index 00000000..3b610a33 --- /dev/null +++ b/app/javascript/controllers/map_preview_controller.js @@ -0,0 +1,67 @@ +import { Controller } from "@hotwired/stimulus" +import L from "leaflet" +import { showFlashMessage } from "../maps/helpers" + +export default class extends Controller { + static targets = ["urlInput", "mapContainer", "saveButton"] + + DEFAULT_TILE_URL = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png' + + connect() { + console.log("Controller connected!") + // Wait for the next frame to ensure the DOM is ready + requestAnimationFrame(() => { + // Force container height + this.mapContainerTarget.style.height = '500px' + this.initializeMap() + }) + } + + initializeMap() { + console.log("Initializing map...") + if (!this.map) { + this.map = L.map(this.mapContainerTarget).setView([51.505, -0.09], 13) + // Invalidate size after initialization + setTimeout(() => { + this.map.invalidateSize() + }, 0) + this.updatePreview() + } + } + + updatePreview() { + console.log("Updating preview...") + const url = this.urlInputTarget.value || this.DEFAULT_TILE_URL + + // Only animate if save button target exists + if (this.hasSaveButtonTarget) { + this.saveButtonTarget.classList.add('btn-animate') + setTimeout(() => { + this.saveButtonTarget.classList.remove('btn-animate') + }, 1000) + } + + if (this.currentLayer) { + this.map.removeLayer(this.currentLayer) + } + + try { + this.currentLayer = L.tileLayer(url, { + maxZoom: 19, + attribution: '© OpenStreetMap contributors' + }).addTo(this.map) + } catch (e) { + console.error('Invalid tile URL:', e) + showFlashMessage('error', 'Invalid tile URL. Reverting to OpenStreetMap.') + + // Reset input to default OSM URL + this.urlInputTarget.value = this.DEFAULT_TILE_URL + + // Create default layer + this.currentLayer = L.tileLayer(this.DEFAULT_TILE_URL, { + maxZoom: 19, + attribution: '© OpenStreetMap contributors' + }).addTo(this.map) + } + } +} diff --git a/app/javascript/controllers/maps_controller.js b/app/javascript/controllers/maps_controller.js index 2e921c86..f08af801 100644 --- a/app/javascript/controllers/maps_controller.js +++ b/app/javascript/controllers/maps_controller.js @@ -385,8 +385,7 @@ export default class extends Controller { baseMaps() { let selectedLayerName = this.userSettings.preferred_map_layer || "OpenStreetMap"; - - return { + let maps = { OpenStreetMap: osmMapLayer(this.map, selectedLayerName), "OpenStreetMap.HOT": osmHotMapLayer(this.map, selectedLayerName), OPNV: OPNVMapLayer(this.map, selectedLayerName), @@ -397,6 +396,33 @@ export default class extends Controller { esriWorldImagery: esriWorldImageryMapLayer(this.map, selectedLayerName), esriWorldGrayCanvas: esriWorldGrayCanvasMapLayer(this.map, selectedLayerName) }; + + // Add custom map if it exists in settings + if (this.userSettings.maps && this.userSettings.maps.url) { + const customLayer = L.tileLayer(this.userSettings.maps.url, { + maxZoom: 19, + attribution: "© OpenStreetMap contributors" + }); + + // If this is the preferred layer, add it to the map immediately + if (selectedLayerName === this.userSettings.maps.name) { + customLayer.addTo(this.map); + // Remove any other base layers that might be active + Object.values(maps).forEach(layer => { + if (this.map.hasLayer(layer)) { + this.map.removeLayer(layer); + } + }); + } + + maps[this.userSettings.maps.name] = customLayer; + } else { + // If no custom map is set, ensure a default layer is added + const defaultLayer = maps[selectedLayerName] || maps["OpenStreetMap"]; + defaultLayer.addTo(this.map); + } + + return maps; } removeEventListeners() { diff --git a/app/models/user.rb b/app/models/user.rb index b8d27f17..b2ffd980 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -16,13 +16,18 @@ class User < ApplicationRecord has_many :trips, dependent: :destroy after_create :create_api_key - before_save :strip_trailing_slashes + before_save :sanitize_input validates :email, presence: true + validates :reset_password_token, uniqueness: true, allow_nil: true attribute :admin, :boolean, default: false + def safe_settings + Users::SafeSettings.new(settings) + end + def countries_visited stats.pluck(:toponyms).flatten.map { _1['country'] }.uniq.compact end @@ -99,8 +104,9 @@ class User < ApplicationRecord save end - def strip_trailing_slashes + def sanitize_input settings['immich_url']&.gsub!(%r{/+\z}, '') settings['photoprism_url']&.gsub!(%r{/+\z}, '') + settings['maps']['url']&.strip! end end diff --git a/app/services/areas/visits/create.rb b/app/services/areas/visits/create.rb index f58bf4b7..768f5f9f 100644 --- a/app/services/areas/visits/create.rb +++ b/app/services/areas/visits/create.rb @@ -6,8 +6,8 @@ class Areas::Visits::Create def initialize(user, areas) @user = user @areas = areas - @time_threshold_minutes = 30 || user.settings['time_threshold_minutes'] - @merge_threshold_minutes = 15 || user.settings['merge_threshold_minutes'] + @time_threshold_minutes = 30 || user.safe_settings.time_threshold_minutes + @merge_threshold_minutes = 15 || user.safe_settings.merge_threshold_minutes end def call diff --git a/app/services/immich/request_photos.rb b/app/services/immich/request_photos.rb index 0d3f6e1f..0dfcbcd5 100644 --- a/app/services/immich/request_photos.rb +++ b/app/services/immich/request_photos.rb @@ -5,15 +5,15 @@ class Immich::RequestPhotos def initialize(user, start_date: '1970-01-01', end_date: nil) @user = user - @immich_api_base_url = URI.parse("#{user.settings['immich_url']}/api/search/metadata") - @immich_api_key = user.settings['immich_api_key'] + @immich_api_base_url = URI.parse("#{user.safe_settings.immich_url}/api/search/metadata") + @immich_api_key = user.safe_settings.immich_api_key @start_date = start_date @end_date = end_date end def call raise ArgumentError, 'Immich API key is missing' if immich_api_key.blank? - raise ArgumentError, 'Immich URL is missing' if user.settings['immich_url'].blank? + raise ArgumentError, 'Immich URL is missing' if user.safe_settings.immich_url.blank? data = retrieve_immich_data diff --git a/app/services/photoprism/request_photos.rb b/app/services/photoprism/request_photos.rb index 276e7e5c..0f7fd93b 100644 --- a/app/services/photoprism/request_photos.rb +++ b/app/services/photoprism/request_photos.rb @@ -9,14 +9,14 @@ class Photoprism::RequestPhotos def initialize(user, start_date: '1970-01-01', end_date: nil) @user = user - @photoprism_api_base_url = URI.parse("#{user.settings['photoprism_url']}/api/v1/photos") - @photoprism_api_key = user.settings['photoprism_api_key'] + @photoprism_api_base_url = URI.parse("#{user.safe_settings.photoprism_url}/api/v1/photos") + @photoprism_api_key = user.safe_settings.photoprism_api_key @start_date = start_date @end_date = end_date end def call - raise ArgumentError, 'Photoprism URL is missing' if user.settings['photoprism_url'].blank? + raise ArgumentError, 'Photoprism URL is missing' if user.safe_settings.photoprism_url.blank? raise ArgumentError, 'Photoprism API key is missing' if photoprism_api_key.blank? data = retrieve_photoprism_data diff --git a/app/services/photos/thumbnail.rb b/app/services/photos/thumbnail.rb index 6bdb7fd5..4f927aad 100644 --- a/app/services/photos/thumbnail.rb +++ b/app/services/photos/thumbnail.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class Photos::Thumbnail + SUPPORTED_SOURCES = %w[immich photoprism].freeze + def initialize(user, source, id) @user = user @source = source @@ -8,6 +10,8 @@ class Photos::Thumbnail end def call + raise unsupported_source_error unless SUPPORTED_SOURCES.include?(source) + HTTParty.get(request_url, headers: headers) end @@ -16,11 +20,11 @@ class Photos::Thumbnail attr_reader :user, :source, :id def source_url - user.settings["#{source}_url"] + user.safe_settings.public_send("#{source}_url") end def source_api_key - user.settings["#{source}_api_key"] + user.safe_settings.public_send("#{source}_api_key") end def source_path @@ -30,8 +34,6 @@ class Photos::Thumbnail when 'photoprism' preview_token = Rails.cache.read("#{Photoprism::CachePreviewToken::TOKEN_CACHE_KEY}_#{user.id}") "/api/v1/t/#{id}/#{preview_token}/tile_500" - else - raise "Unsupported source: #{source}" end end @@ -48,4 +50,8 @@ class Photos::Thumbnail request_headers end + + def unsupported_source_error + raise ArgumentError, "Unsupported source: #{source}" + end end diff --git a/app/services/users/safe_settings.rb b/app/services/users/safe_settings.rb new file mode 100644 index 00000000..64d41b12 --- /dev/null +++ b/app/services/users/safe_settings.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +class Users::SafeSettings + attr_reader :settings + + def initialize(settings) + @settings = settings + end + + # rubocop:disable Metrics/MethodLength + def config + { + fog_of_war_meters: fog_of_war_meters, + meters_between_routes: meters_between_routes, + preferred_map_layer: preferred_map_layer, + speed_colored_routes: speed_colored_routes, + points_rendering_mode: points_rendering_mode, + minutes_between_routes: minutes_between_routes, + time_threshold_minutes: time_threshold_minutes, + merge_threshold_minutes: merge_threshold_minutes, + live_map_enabled: live_map_enabled, + route_opacity: route_opacity, + immich_url: immich_url, + immich_api_key: immich_api_key, + photoprism_url: photoprism_url, + photoprism_api_key: photoprism_api_key, + maps: maps + } + end + # rubocop:enable Metrics/MethodLength + + def fog_of_war_meters + settings['fog_of_war_meters'] || 50 + end + + def meters_between_routes + settings['meters_between_routes'] || 500 + end + + def preferred_map_layer + settings['preferred_map_layer'] || 'OpenStreetMap' + end + + def speed_colored_routes + settings['speed_colored_routes'] || false + end + + def points_rendering_mode + settings['points_rendering_mode'] || 'raw' + end + + def minutes_between_routes + settings['minutes_between_routes'] || 30 + end + + def time_threshold_minutes + settings['time_threshold_minutes'] || 30 + end + + def merge_threshold_minutes + settings['merge_threshold_minutes'] || 15 + end + + def live_map_enabled + settings['live_map_enabled'] || true + end + + def route_opacity + settings['route_opacity'] || 0.6 + end + + def immich_url + settings['immich_url'] + end + + def immich_api_key + settings['immich_api_key'] + end + + def photoprism_url + settings['photoprism_url'] + end + + def photoprism_api_key + settings['photoprism_api_key'] + end + + def maps + settings['maps'] || {} + end +end diff --git a/app/views/imports/index.html.erb b/app/views/imports/index.html.erb index d2ee8d30..97f82a38 100644 --- a/app/views/imports/index.html.erb +++ b/app/views/imports/index.html.erb @@ -5,12 +5,12 @@

Imports

<%= link_to "New import", new_import_path, class: "rounded-lg py-3 px-5 bg-blue-600 text-white block font-medium" %> - <% if current_user.settings['immich_url'] && current_user.settings['immich_api_key'] %> + <% if current_user.safe_settings.immich_url && current_user.safe_settings.immich_api_key %> <%= link_to 'Import Immich data', settings_background_jobs_path(job_name: 'start_immich_import'), method: :post, data: { confirm: 'Are you sure?', turbo_confirm: 'Are you sure?', turbo_method: :post }, class: 'rounded-lg py-3 px-5 bg-blue-600 text-white block font-medium' %> <% else %> Import Immich data <% end %> - <% if current_user.settings['photoprism_url'] && current_user.settings['photoprism_api_key'] %> + <% if current_user.safe_settings.photoprism_url && current_user.safe_settings.photoprism_api_key %> <%= link_to 'Import Photoprism data', settings_background_jobs_path(job_name: 'start_photoprism_import'), method: :post, data: { confirm: 'Are you sure?', turbo_confirm: 'Are you sure?', turbo_method: :post }, class: 'rounded-lg py-3 px-5 bg-blue-600 text-white block font-medium' %> <% else %> Import Photoprism data diff --git a/app/views/map/index.html.erb b/app/views/map/index.html.erb index d3c39f80..9fa4a0fe 100644 --- a/app/views/map/index.html.erb +++ b/app/views/map/index.html.erb @@ -49,7 +49,7 @@ data-points-target="map" data-distance_unit="<%= DISTANCE_UNIT %>" data-api_key="<%= current_user.api_key %>" - data-user_settings=<%= current_user.settings.to_json %> + data-user_settings='<%= current_user.settings.to_json.html_safe %>' data-coordinates="<%= @coordinates %>" data-distance="<%= @distance %>" data-points_number="<%= @points_number %>" diff --git a/app/views/settings/_navigation.html.erb b/app/views/settings/_navigation.html.erb index b0b20437..3f5aa627 100644 --- a/app/views/settings/_navigation.html.erb +++ b/app/views/settings/_navigation.html.erb @@ -4,4 +4,5 @@ <%= link_to 'Users', settings_users_path, role: 'tab', class: "tab #{active_tab?(settings_users_path)}" %> <%= link_to 'Background Jobs', settings_background_jobs_path, role: 'tab', class: "tab #{active_tab?(settings_background_jobs_path)}" %> <% end %> + <%= link_to 'Map', settings_maps_path, role: 'tab', class: "tab #{active_tab?(settings_maps_path)}" %> diff --git a/app/views/settings/index.html.erb b/app/views/settings/index.html.erb index 613cfe73..fc828c07 100644 --- a/app/views/settings/index.html.erb +++ b/app/views/settings/index.html.erb @@ -9,20 +9,20 @@ <%= form_for :settings, url: settings_path, method: :patch, data: { turbo_method: :patch, turbo: false } do |f| %>
<%= f.label :immich_url %> - <%= f.text_field :immich_url, value: current_user.settings['immich_url'], class: "input input-bordered", placeholder: 'http://192.168.0.1:2283' %> + <%= f.text_field :immich_url, value: current_user.safe_settings.immich_url, class: "input input-bordered", placeholder: 'http://192.168.0.1:2283' %>
<%= f.label :immich_api_key %> - <%= f.text_field :immich_api_key, value: current_user.settings['immich_api_key'], class: "input input-bordered", placeholder: 'xxxxxxxxxxxxxx' %> + <%= f.text_field :immich_api_key, value: current_user.safe_settings.immich_api_key, class: "input input-bordered", placeholder: 'xxxxxxxxxxxxxx' %>
<%= f.label :photoprism_url %> - <%= f.text_field :photoprism_url, value: current_user.settings['photoprism_url'], class: "input input-bordered", placeholder: 'http://192.168.0.1:2342' %> + <%= f.text_field :photoprism_url, value: current_user.safe_settings.photoprism_url, class: "input input-bordered", placeholder: 'http://192.168.0.1:2342' %>
<%= f.label :photoprism_api_key %> - <%= f.text_field :photoprism_api_key, value: current_user.settings['photoprism_api_key'], class: "input input-bordered", placeholder: 'xxxxxxxxxxxxxx' %> + <%= f.text_field :photoprism_api_key, value: current_user.safe_settings.photoprism_api_key, class: "input input-bordered", placeholder: 'xxxxxxxxxxxxxx' %>
diff --git a/app/views/settings/maps/index.html.erb b/app/views/settings/maps/index.html.erb new file mode 100644 index 00000000..b295028c --- /dev/null +++ b/app/views/settings/maps/index.html.erb @@ -0,0 +1,45 @@ +<% content_for :title, "Background jobs" %> + +
+ <%= render 'settings/navigation' %> + +
+

Maps settings

+
+ +
+ <%= form_for :maps, + url: settings_maps_path, + method: :patch, + autocomplete: "off", + data: { turbo_method: :patch, turbo: false }, + class: "lg:col-span-1" do |f| %> +
+ <%= f.label :name %> + <%= f.text_field :name, value: @maps['name'], placeholder: 'Example: OpenStreetMap', class: "input input-bordered" %> +
+ +
+ <%= f.label :url, 'URL' %> + <%= f.text_field :url, + value: @maps['url'], + autocomplete: "off", + placeholder: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + class: "input input-bordered", + data: { + map_preview_target: "urlInput", + action: "input->map-preview#updatePreview" + } %> +
+ + <%= f.submit 'Save', class: "btn btn-primary", data: { map_preview_target: "saveButton" } %> + <% end %> + +
+
+
+
+
diff --git a/config/routes.rb b/config/routes.rb index 9a6a38ca..c3ef1717 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -20,6 +20,8 @@ Rails.application.routes.draw do namespace :settings do resources :background_jobs, only: %i[index create destroy] resources :users, only: %i[index create destroy edit update] + resources :maps, only: %i[index] + patch 'maps', to: 'maps#update' end patch 'settings', to: 'settings#update' diff --git a/spec/requests/settings/maps_spec.rb b/spec/requests/settings/maps_spec.rb new file mode 100644 index 00000000..4e641945 --- /dev/null +++ b/spec/requests/settings/maps_spec.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'settings/maps', type: :request do + before do + stub_request(:any, 'https://api.github.com/repos/Freika/dawarich/tags') + .to_return(status: 200, body: '[{"name": "1.0.0"}]', headers: {}) + end + + context 'when user is authenticated' do + let!(:user) { create(:user) } + + before do + sign_in user + end + + describe 'GET /index' do + it 'returns a success response' do + get settings_maps_url + + expect(response).to be_successful + end + end + + describe 'PATCH /update' do + it 'returns a success response' do + patch settings_maps_path, params: { maps: { name: 'Test', url: 'https://test.com' } } + + expect(response).to redirect_to(settings_maps_path) + expect(user.settings['maps']).to eq({ 'name' => 'Test', 'url' => 'https://test.com' }) + end + end + end + + context 'when user is not authenticated' do + it 'redirects to the sign in page' do + get settings_maps_path + + expect(response).to redirect_to(new_user_session_path) + end + end +end diff --git a/spec/services/photos/thumbnail_spec.rb b/spec/services/photos/thumbnail_spec.rb index c687e370..00c64a07 100644 --- a/spec/services/photos/thumbnail_spec.rb +++ b/spec/services/photos/thumbnail_spec.rb @@ -70,7 +70,7 @@ RSpec.describe Photos::Thumbnail do let(:source) { 'unsupported' } it 'raises an error' do - expect { subject }.to raise_error(RuntimeError, 'Unsupported source: unsupported') + expect { subject }.to raise_error(ArgumentError, 'Unsupported source: unsupported') end end end From d2d6f95322af0ce99b4c3709587efca250f2a97a Mon Sep 17 00:00:00 2001 From: Eugene Burmakin Date: Mon, 10 Feb 2025 20:48:16 +0100 Subject: [PATCH 03/18] Fix accessing nested settings --- app/models/user.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/user.rb b/app/models/user.rb index b2ffd980..97fb9fe0 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -107,6 +107,6 @@ class User < ApplicationRecord def sanitize_input settings['immich_url']&.gsub!(%r{/+\z}, '') settings['photoprism_url']&.gsub!(%r{/+\z}, '') - settings['maps']['url']&.strip! + settings.try(:[], 'maps')&.try(:[], 'url')&.strip! end end From 01fd9f6e35ff098ff4c3c12937f28e2a4f717034 Mon Sep 17 00:00:00 2001 From: Christian Nikel Date: Tue, 11 Feb 2025 00:12:01 +0000 Subject: [PATCH 04/18] Fix fog gets displaced when dragging map Also recalculates the size of the fog when resizing the browser window. Closes #774 --- app/javascript/maps/fog_of_war.js | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/app/javascript/maps/fog_of_war.js b/app/javascript/maps/fog_of_war.js index 8e910274..bc3acbb1 100644 --- a/app/javascript/maps/fog_of_war.js +++ b/app/javascript/maps/fog_of_war.js @@ -85,15 +85,12 @@ export function createFogOverlay() { onAdd: (map) => { initializeFogCanvas(map); - // Add drag event handlers to update fog during marker movement - map.on('drag', () => { - const fog = document.getElementById('fog'); - if (fog) { - // Update fog canvas position to match map position - const mapPos = map.getContainer().getBoundingClientRect(); - fog.style.left = `${mapPos.left}px`; - fog.style.top = `${mapPos.top}px`; - } + // Add resize event handlers to update fog size + map.on('resize', () => { + // Set canvas size to match map container + const mapSize = map.getSize(); + fog.width = mapSize.x; + fog.height = mapSize.y; }); }, onRemove: (map) => { @@ -102,7 +99,7 @@ export function createFogOverlay() { fog.remove(); } // Clean up event listener - map.off('drag'); + map.off('resize'); } }); } From 1580fb8ade978a582ccb2a7204cf5e3a54e07251 Mon Sep 17 00:00:00 2001 From: Eugene Burmakin Date: Tue, 11 Feb 2025 20:45:36 +0100 Subject: [PATCH 05/18] Export map tiles usage to Prometheus --- CHANGELOG.md | 7 ++ Procfile.prometheus.dev | 2 +- README.md | 1 + .../api/v1/tile_usages_controller.rb | 9 +++ app/javascript/controllers/maps_controller.js | 26 ++++++- app/javascript/maps/tile_monitor.js | 67 +++++++++++++++++++ app/services/tile_usage/track.rb | 19 ++++++ app/views/map/index.html.erb | 1 + app/views/settings/maps/index.html.erb | 17 ++++- config/initializers/03_dawarich_settings.rb | 11 ++- config/initializers/prometheus.rb | 2 +- config/routes.rb | 2 + 12 files changed, 152 insertions(+), 12 deletions(-) create mode 100644 app/controllers/api/v1/tile_usages_controller.rb create mode 100644 app/javascript/maps/tile_monitor.js create mode 100644 app/services/tile_usage/track.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 30ba22d1..269617f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,13 @@ To set a custom tile URL, go to the user settings and set the `Maps` section to - Safe settings for user with default values. - In the user settings, you can now set a custom tile URL for the map. #429 #715 +- If you have Prometheus exporter enabled, you can now see a `ruby_dawarich_map_tiles` metric in Prometheus, which shows the total number of map tiles loaded. Example: + +``` +# HELP ruby_dawarich_map_tiles +# TYPE ruby_dawarich_map_tiles gauge +ruby_dawarich_map_tiles 99 +``` # 0.24.0 - 2025-02-10 diff --git a/Procfile.prometheus.dev b/Procfile.prometheus.dev index 71fe0374..95a12639 100644 --- a/Procfile.prometheus.dev +++ b/Procfile.prometheus.dev @@ -1,2 +1,2 @@ prometheus_exporter: bundle exec prometheus_exporter -b ANY -web: bin/rails server -p 3000 -b :: \ No newline at end of file +web: bin/rails server -p 3000 -b :: diff --git a/README.md b/README.md index 0d21ed03..5aa76a1b 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ Donate using crypto: [0x6bAd13667692632f1bF926cA9B421bEe7EaEB8D4](https://ethers - Explore statistics like the number of countries and cities visited, total distance traveled, and more! 📄 **Changelog**: Find the latest updates [here](CHANGELOG.md). + 👩‍💻 **Contribute**: See [CONTRIBUTING.md](CONTRIBUTING.md) for how to contribute to Dawarich. --- diff --git a/app/controllers/api/v1/tile_usages_controller.rb b/app/controllers/api/v1/tile_usages_controller.rb new file mode 100644 index 00000000..d5d74d9d --- /dev/null +++ b/app/controllers/api/v1/tile_usages_controller.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class Api::V1::TileUsagesController < ApiController + def create + TileUsage::Track.new(params[:tile_count].to_i).call + + head :ok + end +end diff --git a/app/javascript/controllers/maps_controller.js b/app/javascript/controllers/maps_controller.js index f08af801..53b39c20 100644 --- a/app/javascript/controllers/maps_controller.js +++ b/app/javascript/controllers/maps_controller.js @@ -8,9 +8,7 @@ import { createMarkersArray } from "../maps/markers"; import { createPolylinesLayer, updatePolylinesOpacity, - updatePolylinesColors, - calculateSpeed, - getSpeedColor + updatePolylinesColors } from "../maps/polylines"; import { fetchAndDrawAreas, handleAreaCreated } from "../maps/areas"; @@ -32,9 +30,13 @@ import { countryCodesMap } from "../maps/country_codes"; import "leaflet-draw"; import { initializeFogCanvas, drawFogCanvas, createFogOverlay } from "../maps/fog_of_war"; +import { TileMonitor } from "../maps/tile_monitor"; export default class extends Controller { static targets = ["container"]; + static values = { + monitoringEnabled: Boolean + } settingsButtonAdded = false; layerControl = null; @@ -245,6 +247,19 @@ export default class extends Controller { if (this.liveMapEnabled) { this.setupSubscription(); } + + // Initialize tile monitor + this.tileMonitor = new TileMonitor(this.monitoringEnabledValue, this.apiKey); + + // Add tile load event handlers to each base layer + Object.entries(this.baseMaps()).forEach(([name, layer]) => { + layer.on('tileload', () => { + this.tileMonitor.recordTileLoad(name); + }); + }); + + // Start monitoring + this.tileMonitor.startMonitoring(); } disconnect() { @@ -260,6 +275,11 @@ export default class extends Controller { if (this.map) { this.map.remove(); } + + // Stop tile monitoring + if (this.tileMonitor) { + this.tileMonitor.stopMonitoring(); + } } setupSubscription() { diff --git a/app/javascript/maps/tile_monitor.js b/app/javascript/maps/tile_monitor.js new file mode 100644 index 00000000..bd5da516 --- /dev/null +++ b/app/javascript/maps/tile_monitor.js @@ -0,0 +1,67 @@ +export class TileMonitor { + constructor(monitoringEnabled, apiKey) { + this.monitoringEnabled = monitoringEnabled; + this.apiKey = apiKey; + this.tileQueue = 0; + this.tileUpdateInterval = null; + } + + startMonitoring() { + // Only start the interval if monitoring is enabled + if (!this.monitoringEnabled) return; + + // Clear any existing interval + if (this.tileUpdateInterval) { + clearInterval(this.tileUpdateInterval); + } + + // Set up a regular interval to send stats + this.tileUpdateInterval = setInterval(() => { + this.sendTileUsage(); + }, 5000); // Exactly every 5 seconds + } + + stopMonitoring() { + if (this.tileUpdateInterval) { + clearInterval(this.tileUpdateInterval); + this.sendTileUsage(); // Send any remaining stats + } + } + + recordTileLoad() { + if (!this.monitoringEnabled) return; + this.tileQueue += 1; + } + + sendTileUsage() { + // Don't send if monitoring is disabled or queue is empty + if (!this.monitoringEnabled || this.tileQueue === 0) return; + + const currentCount = this.tileQueue; + console.log('Sending tile usage batch:', currentCount); + + fetch('/api/v1/tile_usages', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${this.apiKey}` + }, + body: JSON.stringify({ + tile_count: currentCount + }) + }) + .then(response => { + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + // Only subtract sent count if it hasn't changed + if (this.tileQueue === currentCount) { + this.tileQueue = 0; + } else { + this.tileQueue -= currentCount; + } + console.log('Tile usage batch sent successfully'); + }) + .catch(error => console.error('Error recording tile usage:', error)); + } +} diff --git a/app/services/tile_usage/track.rb b/app/services/tile_usage/track.rb new file mode 100644 index 00000000..69d1b361 --- /dev/null +++ b/app/services/tile_usage/track.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +class TileUsage::Track + def initialize(count = 1) + @count = count + end + + def call + metric_data = { + type: 'counter', + name: 'dawarich_map_tiles', + value: @count + } + + PrometheusExporter::Client.default.send_json(metric_data) + rescue StandardError => e + Rails.logger.error("Failed to send tile usage metric: #{e.message}") + end +end diff --git a/app/views/map/index.html.erb b/app/views/map/index.html.erb index 9fa4a0fe..511f12a7 100644 --- a/app/views/map/index.html.erb +++ b/app/views/map/index.html.erb @@ -53,6 +53,7 @@ data-coordinates="<%= @coordinates %>" data-distance="<%= @distance %>" data-points_number="<%= @points_number %>" + data-maps-monitoring-enabled-value="<%= DawarichSettings.prometheus_exporter_enabled? %>" data-timezone="<%= Rails.configuration.time_zone %>">
diff --git a/app/views/settings/maps/index.html.erb b/app/views/settings/maps/index.html.erb index b295028c..2e50950d 100644 --- a/app/views/settings/maps/index.html.erb +++ b/app/views/settings/maps/index.html.erb @@ -3,10 +3,25 @@
<%= render 'settings/navigation' %> -
+

Maps settings

+ +
<%= form_for :maps, url: settings_maps_path, diff --git a/config/initializers/03_dawarich_settings.rb b/config/initializers/03_dawarich_settings.rb index 451ed716..589d9ef1 100644 --- a/config/initializers/03_dawarich_settings.rb +++ b/config/initializers/03_dawarich_settings.rb @@ -18,12 +18,11 @@ class DawarichSettings @geoapify_enabled ||= GEOAPIFY_API_KEY.present? end - def meters_between_tracks - @meters_between_tracks ||= 300 - end - - def minutes_between_tracks - @minutes_between_tracks ||= 20 + def prometheus_exporter_enabled? + @prometheus_exporter_enabled ||= + ENV['PROMETHEUS_EXPORTER_ENABLED'].to_s == 'true' && + ENV['PROMETHEUS_EXPORTER_HOST'].present? && + ENV['PROMETHEUS_EXPORTER_PORT'].present? end end end diff --git a/config/initializers/prometheus.rb b/config/initializers/prometheus.rb index 3573fb84..1a2f38e0 100644 --- a/config/initializers/prometheus.rb +++ b/config/initializers/prometheus.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -if !Rails.env.test? && ENV['PROMETHEUS_EXPORTER_ENABLED'].to_s == 'true' +if !Rails.env.test? && DawarichSettings.prometheus_exporter_enabled? require 'prometheus_exporter/middleware' require 'prometheus_exporter/instrumentation' diff --git a/config/routes.rb b/config/routes.rb index c3ef1717..45b55576 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -96,6 +96,8 @@ Rails.application.routes.draw do get 'thumbnail', constraints: { id: %r{[^/]+} } end end + + resources :tile_usages, only: [:create] end end end From b2e6a141fc347828c3dd6f819e0d56e83224b5a0 Mon Sep 17 00:00:00 2001 From: Eugene Burmakin Date: Tue, 11 Feb 2025 21:04:12 +0100 Subject: [PATCH 06/18] Rehash paths and add tests --- .../api/v1/maps/tile_usage_controller.rb | 15 ++++++++ .../api/v1/tile_usages_controller.rb | 9 ----- app/javascript/maps/tile_monitor.js | 6 ++- app/services/{ => maps}/tile_usage/track.rb | 2 +- config/routes.rb | 4 +- spec/requests/api/v1/maps/tile_usage_spec.rb | 37 +++++++++++++++++++ spec/services/maps/tile_usage/track_spec.rb | 29 +++++++++++++++ 7 files changed, 89 insertions(+), 13 deletions(-) create mode 100644 app/controllers/api/v1/maps/tile_usage_controller.rb delete mode 100644 app/controllers/api/v1/tile_usages_controller.rb rename app/services/{ => maps}/tile_usage/track.rb (92%) create mode 100644 spec/requests/api/v1/maps/tile_usage_spec.rb create mode 100644 spec/services/maps/tile_usage/track_spec.rb diff --git a/app/controllers/api/v1/maps/tile_usage_controller.rb b/app/controllers/api/v1/maps/tile_usage_controller.rb new file mode 100644 index 00000000..43f8f070 --- /dev/null +++ b/app/controllers/api/v1/maps/tile_usage_controller.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class Api::V1::Maps::TileUsageController < ApiController + def create + Maps::TileUsage::Track.new(tile_usage_params[:count].to_i).call + + head :ok + end + + private + + def tile_usage_params + params.require(:tile_usage).permit(:count) + end +end diff --git a/app/controllers/api/v1/tile_usages_controller.rb b/app/controllers/api/v1/tile_usages_controller.rb deleted file mode 100644 index d5d74d9d..00000000 --- a/app/controllers/api/v1/tile_usages_controller.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -class Api::V1::TileUsagesController < ApiController - def create - TileUsage::Track.new(params[:tile_count].to_i).call - - head :ok - end -end diff --git a/app/javascript/maps/tile_monitor.js b/app/javascript/maps/tile_monitor.js index bd5da516..3a4ff36e 100644 --- a/app/javascript/maps/tile_monitor.js +++ b/app/javascript/maps/tile_monitor.js @@ -40,14 +40,16 @@ export class TileMonitor { const currentCount = this.tileQueue; console.log('Sending tile usage batch:', currentCount); - fetch('/api/v1/tile_usages', { + fetch('/api/v1/maps/tile_usage', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${this.apiKey}` }, body: JSON.stringify({ - tile_count: currentCount + tile_usage: { + count: currentCount + } }) }) .then(response => { diff --git a/app/services/tile_usage/track.rb b/app/services/maps/tile_usage/track.rb similarity index 92% rename from app/services/tile_usage/track.rb rename to app/services/maps/tile_usage/track.rb index 69d1b361..13fd1711 100644 --- a/app/services/tile_usage/track.rb +++ b/app/services/maps/tile_usage/track.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class TileUsage::Track +class Maps::TileUsage::Track def initialize(count = 1) @count = count end diff --git a/config/routes.rb b/config/routes.rb index 45b55576..09ca7fbc 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -97,7 +97,9 @@ Rails.application.routes.draw do end end - resources :tile_usages, only: [:create] + namespace :maps do + resources :tile_usage, only: [:create] + end end end end diff --git a/spec/requests/api/v1/maps/tile_usage_spec.rb b/spec/requests/api/v1/maps/tile_usage_spec.rb new file mode 100644 index 00000000..8c35d9a6 --- /dev/null +++ b/spec/requests/api/v1/maps/tile_usage_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Api::V1::Maps::TileUsage', type: :request do + describe 'POST /api/v1/maps/tile_usage' do + let(:tile_count) { 5 } + let(:track_service) { instance_double(Maps::TileUsage::Track) } + + before do + allow(Maps::TileUsage::Track).to receive(:new).with(tile_count).and_return(track_service) + allow(track_service).to receive(:call) + end + + context 'when user is authenticated' do + let(:user) { create(:user) } + + it 'tracks tile usage' do + post '/api/v1/maps/tile_usage', + params: { tile_usage: { count: tile_count } }, + headers: { 'Authorization' => "Bearer #{user.api_key}" } + + expect(Maps::TileUsage::Track).to have_received(:new).with(tile_count) + expect(track_service).to have_received(:call) + expect(response).to have_http_status(:ok) + end + end + + context 'when user is not authenticated' do + it 'returns unauthorized' do + post '/api/v1/maps/tile_usage', params: { tile_usage: { count: tile_count } } + + expect(response).to have_http_status(:unauthorized) + end + end + end +end diff --git a/spec/services/maps/tile_usage/track_spec.rb b/spec/services/maps/tile_usage/track_spec.rb new file mode 100644 index 00000000..82f0360c --- /dev/null +++ b/spec/services/maps/tile_usage/track_spec.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'prometheus_exporter/client' + +RSpec.describe Maps::TileUsage::Track do + describe '#call' do + subject(:track) { described_class.new(tile_count).call } + + let(:tile_count) { 5 } + let(:prometheus_client) { instance_double(PrometheusExporter::Client) } + + before do + allow(PrometheusExporter::Client).to receive(:default).and_return(prometheus_client) + end + + it 'tracks tile usage' do + expect(prometheus_client).to receive(:send_json).with( + { + type: 'counter', + name: 'dawarich_map_tiles', + value: tile_count + } + ) + + track + end + end +end From 06da91df27d2c124038f57de14e5b9d73d03dcb1 Mon Sep 17 00:00:00 2001 From: Eugene Burmakin Date: Tue, 11 Feb 2025 21:12:35 +0100 Subject: [PATCH 07/18] Fix live map enabled default value --- app/services/users/safe_settings.rb | 4 +- spec/services/users/safe_settings_spec.rb | 143 ++++++++++++++++++++++ 2 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 spec/services/users/safe_settings_spec.rb diff --git a/app/services/users/safe_settings.rb b/app/services/users/safe_settings.rb index 64d41b12..dab0a516 100644 --- a/app/services/users/safe_settings.rb +++ b/app/services/users/safe_settings.rb @@ -62,7 +62,9 @@ class Users::SafeSettings end def live_map_enabled - settings['live_map_enabled'] || true + return settings['live_map_enabled'] if settings.key?('live_map_enabled') + + true end def route_opacity diff --git a/spec/services/users/safe_settings_spec.rb b/spec/services/users/safe_settings_spec.rb new file mode 100644 index 00000000..aaafbac9 --- /dev/null +++ b/spec/services/users/safe_settings_spec.rb @@ -0,0 +1,143 @@ +# frozen_string_literal: true + +RSpec.describe Users::SafeSettings do + describe '#config' do + context 'with default values' do + let(:settings) { {} } + let(:safe_settings) { described_class.new(settings) } + + it 'returns default configuration' do + expect(safe_settings.config).to eq( + { + fog_of_war_meters: 50, + meters_between_routes: 500, + preferred_map_layer: 'OpenStreetMap', + speed_colored_routes: false, + points_rendering_mode: 'raw', + minutes_between_routes: 30, + time_threshold_minutes: 30, + merge_threshold_minutes: 15, + live_map_enabled: true, + route_opacity: 0.6, + immich_url: nil, + immich_api_key: nil, + photoprism_url: nil, + photoprism_api_key: nil, + maps: {} + } + ) + end + end + + context 'with custom values' do + let(:settings) do + { + 'fog_of_war_meters' => 100, + 'meters_between_routes' => 1000, + 'preferred_map_layer' => 'Satellite', + 'speed_colored_routes' => true, + 'points_rendering_mode' => 'simplified', + 'minutes_between_routes' => 60, + 'time_threshold_minutes' => 45, + 'merge_threshold_minutes' => 20, + 'live_map_enabled' => false, + 'route_opacity' => 0.8, + 'immich_url' => 'https://immich.example.com', + 'immich_api_key' => 'immich-key', + 'photoprism_url' => 'https://photoprism.example.com', + 'photoprism_api_key' => 'photoprism-key', + 'maps' => { 'name' => 'custom', 'url' => 'https://custom.example.com' } + } + end + let(:safe_settings) { described_class.new(settings) } + + it 'returns custom configuration' do + expect(safe_settings.config).to eq( + { + fog_of_war_meters: 100, + meters_between_routes: 1000, + preferred_map_layer: 'Satellite', + speed_colored_routes: true, + points_rendering_mode: 'simplified', + minutes_between_routes: 60, + time_threshold_minutes: 45, + merge_threshold_minutes: 20, + live_map_enabled: false, + route_opacity: 0.8, + immich_url: 'https://immich.example.com', + immich_api_key: 'immich-key', + photoprism_url: 'https://photoprism.example.com', + photoprism_api_key: 'photoprism-key', + maps: { 'name' => 'custom', 'url' => 'https://custom.example.com' } + } + ) + end + end + end + + describe 'individual settings' do + let(:safe_settings) { described_class.new(settings) } + + context 'with default values' do + let(:settings) { {} } + + it 'returns default values for each setting' do + expect(safe_settings.fog_of_war_meters).to eq(50) + expect(safe_settings.meters_between_routes).to eq(500) + expect(safe_settings.preferred_map_layer).to eq('OpenStreetMap') + expect(safe_settings.speed_colored_routes).to be false + expect(safe_settings.points_rendering_mode).to eq('raw') + expect(safe_settings.minutes_between_routes).to eq(30) + expect(safe_settings.time_threshold_minutes).to eq(30) + expect(safe_settings.merge_threshold_minutes).to eq(15) + expect(safe_settings.live_map_enabled).to be true + expect(safe_settings.route_opacity).to eq(0.6) + expect(safe_settings.immich_url).to be_nil + expect(safe_settings.immich_api_key).to be_nil + expect(safe_settings.photoprism_url).to be_nil + expect(safe_settings.photoprism_api_key).to be_nil + expect(safe_settings.maps).to eq({}) + end + end + + context 'with custom values' do + let(:settings) do + { + 'fog_of_war_meters' => 100, + 'meters_between_routes' => 1000, + 'preferred_map_layer' => 'Satellite', + 'speed_colored_routes' => true, + 'points_rendering_mode' => 'simplified', + 'minutes_between_routes' => 60, + 'time_threshold_minutes' => 45, + 'merge_threshold_minutes' => 20, + 'live_map_enabled' => false, + 'route_opacity' => 0.8, + 'immich_url' => 'https://immich.example.com', + 'immich_api_key' => 'immich-key', + 'photoprism_url' => 'https://photoprism.example.com', + 'photoprism_api_key' => 'photoprism-key', + 'maps' => { 'name' => 'custom', 'url' => 'https://custom.example.com' } + } + end + + it 'returns custom values for each setting' do + expect(safe_settings.fog_of_war_meters).to eq(100) + expect(safe_settings.meters_between_routes).to eq(1000) + expect(safe_settings.preferred_map_layer).to eq('Satellite') + expect(safe_settings.speed_colored_routes).to be true + expect(safe_settings.points_rendering_mode).to eq('simplified') + expect(safe_settings.minutes_between_routes).to eq(60) + expect(safe_settings.time_threshold_minutes).to eq(45) + expect(safe_settings.merge_threshold_minutes).to eq(20) + expect(safe_settings.live_map_enabled).to be false + expect(safe_settings.route_opacity).to eq(0.8) + expect(safe_settings.immich_url).to eq('https://immich.example.com') + expect(safe_settings.immich_api_key).to eq('immich-key') + expect(safe_settings.photoprism_url).to eq('https://photoprism.example.com') + expect(safe_settings.photoprism_api_key).to eq('photoprism-key') + expect(safe_settings.maps).to eq({ 'name' => 'custom', 'url' => 'https://custom.example.com' }) + end + end + end +end From 5b2834599ee9d100af29633db9ca5d49cc9f4956 Mon Sep 17 00:00:00 2001 From: Eugene Burmakin Date: Tue, 11 Feb 2025 21:17:33 +0100 Subject: [PATCH 08/18] Update prometheus metric name --- CHANGELOG.md | 6 +++--- app/services/maps/tile_usage/track.rb | 2 +- spec/services/maps/tile_usage/track_spec.rb | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 269617f7..76df144a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,9 +19,9 @@ To set a custom tile URL, go to the user settings and set the `Maps` section to - If you have Prometheus exporter enabled, you can now see a `ruby_dawarich_map_tiles` metric in Prometheus, which shows the total number of map tiles loaded. Example: ``` -# HELP ruby_dawarich_map_tiles -# TYPE ruby_dawarich_map_tiles gauge -ruby_dawarich_map_tiles 99 +# HELP ruby_dawarich_map_tiles_usage +# TYPE ruby_dawarich_map_tiles_usage counter +ruby_dawarich_map_tiles_usage 99 ``` # 0.24.0 - 2025-02-10 diff --git a/app/services/maps/tile_usage/track.rb b/app/services/maps/tile_usage/track.rb index 13fd1711..0affd754 100644 --- a/app/services/maps/tile_usage/track.rb +++ b/app/services/maps/tile_usage/track.rb @@ -8,7 +8,7 @@ class Maps::TileUsage::Track def call metric_data = { type: 'counter', - name: 'dawarich_map_tiles', + name: 'dawarich_map_tiles_usage', value: @count } diff --git a/spec/services/maps/tile_usage/track_spec.rb b/spec/services/maps/tile_usage/track_spec.rb index 82f0360c..896810eb 100644 --- a/spec/services/maps/tile_usage/track_spec.rb +++ b/spec/services/maps/tile_usage/track_spec.rb @@ -18,7 +18,7 @@ RSpec.describe Maps::TileUsage::Track do expect(prometheus_client).to receive(:send_json).with( { type: 'counter', - name: 'dawarich_map_tiles', + name: 'dawarich_map_tiles_usage', value: tile_count } ) From 8997d10658078267a4d90260bea204119300f07c Mon Sep 17 00:00:00 2001 From: Eugene Burmakin Date: Tue, 11 Feb 2025 21:38:41 +0100 Subject: [PATCH 09/18] Fix speed units on the Points page --- .app_version | 2 +- CHANGELOG.md | 6 +++++- app/helpers/application_helper.rb | 6 ++++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/.app_version b/.app_version index 2094a100..48b91fd8 100644 --- a/.app_version +++ b/.app_version @@ -1 +1 @@ -0.24.0 +0.24.1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 76df144a..233e7708 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ 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/). -# 0.24.1 - 2025-02-10 +# 0.24.1 - 2025-02-11 ## Custom map tiles @@ -24,6 +24,10 @@ To set a custom tile URL, go to the user settings and set the `Maps` section to ruby_dawarich_map_tiles_usage 99 ``` +### Fixed + +- Speed on the Points page is now being displayed in kilometers per hour. + # 0.24.0 - 2025-02-10 ## Points speed units diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index c6258d08..a4a01a5e 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -120,4 +120,10 @@ module ApplicationHelper 'text-red-500' end + + def point_speed(speed) + return speed if speed.to_i <= 0 + + speed * 3.6 + end end From 2a36f961109ff4c016d7186db681fedff5adb235 Mon Sep 17 00:00:00 2001 From: Eugene Burmakin Date: Tue, 11 Feb 2025 21:39:04 +0100 Subject: [PATCH 10/18] Provide reference to the issue in the changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 233e7708..6fb1084b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,7 @@ ruby_dawarich_map_tiles_usage 99 ### Fixed -- Speed on the Points page is now being displayed in kilometers per hour. +- Speed on the Points page is now being displayed in kilometers per hour. #700 # 0.24.0 - 2025-02-10 From 93dbde7e79313c6cb53a74d1ab4cd281410424d9 Mon Sep 17 00:00:00 2001 From: Evgenii Burmakin Date: Wed, 12 Feb 2025 22:18:22 +0100 Subject: [PATCH 11/18] Revert "fix: set dbname in psql entrypoint commands" --- docker/sidekiq-entrypoint.sh | 2 +- docker/web-entrypoint.sh | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docker/sidekiq-entrypoint.sh b/docker/sidekiq-entrypoint.sh index cc4e20cd..1083891b 100644 --- a/docker/sidekiq-entrypoint.sh +++ b/docker/sidekiq-entrypoint.sh @@ -24,7 +24,7 @@ fi # Wait for the database to become available echo "⏳ Waiting for database to be ready..." -until PGPASSWORD=$DATABASE_PASSWORD psql -h "$DATABASE_HOST" -p "$DATABASE_PORT" -U "$DATABASE_USERNAME" -d "$DATABASE_NAME" -c '\q'; do +until PGPASSWORD=$DATABASE_PASSWORD psql -h "$DATABASE_HOST" -p "$DATABASE_PORT" -U "$DATABASE_USERNAME" -c '\q'; do >&2 echo "Postgres is unavailable - retrying..." sleep 2 done diff --git a/docker/web-entrypoint.sh b/docker/web-entrypoint.sh index 5c82d1b0..230f91cc 100644 --- a/docker/web-entrypoint.sh +++ b/docker/web-entrypoint.sh @@ -29,14 +29,14 @@ rm -f $APP_PATH/tmp/pids/server.pid # Wait for the database to become available echo "⏳ Waiting for database to be ready..." -until PGPASSWORD=$DATABASE_PASSWORD psql -h "$DATABASE_HOST" -p "$DATABASE_PORT" -U "$DATABASE_USERNAME" -d "$DATABASE_NAME" -c '\q'; do +until PGPASSWORD=$DATABASE_PASSWORD psql -h "$DATABASE_HOST" -p "$DATABASE_PORT" -U "$DATABASE_USERNAME" -c '\q'; do >&2 echo "Postgres is unavailable - retrying..." sleep 2 done echo "✅ PostgreSQL is ready!" # Create database if it doesn't exist -if ! PGPASSWORD=$DATABASE_PASSWORD psql -h "$DATABASE_HOST" -p "$DATABASE_PORT" -U "$DATABASE_USERNAME" -d "$DATABASE_NAME" -c "SELECT 1 FROM pg_database WHERE datname='$DATABASE_NAME'" | grep -q 1; then +if ! PGPASSWORD=$DATABASE_PASSWORD psql -h "$DATABASE_HOST" -p "$DATABASE_PORT" -U "$DATABASE_USERNAME" -c "SELECT 1 FROM pg_database WHERE datname='$DATABASE_NAME'" | grep -q 1; then echo "Creating database $DATABASE_NAME..." bundle exec rails db:create fi From dc7bd841fd4494c439eeaab72731f943a8001000 Mon Sep 17 00:00:00 2001 From: Eugene Burmakin Date: Wed, 12 Feb 2025 22:56:11 +0100 Subject: [PATCH 12/18] Introduce matrix for parallel docker image builds --- .github/workflows/build_and_push.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build_and_push.yml b/.github/workflows/build_and_push.yml index 49580227..c991e46f 100644 --- a/.github/workflows/build_and_push.yml +++ b/.github/workflows/build_and_push.yml @@ -11,8 +11,11 @@ on: types: [created] jobs: - build-and-push-docker: + build-and-push: runs-on: ubuntu-22.04 + strategy: + matrix: + platform: ["linux/amd64", "linux/arm64", "linux/arm/v7", "linux/arm/v6"] steps: - name: Checkout code uses: actions/checkout@v4 @@ -51,10 +54,7 @@ jobs: # Add :rc tag for pre-releases if [ "${{ github.event.release.prerelease }}" = "true" ]; then TAGS="${TAGS},freikin/dawarich:rc" - fi - - # Add :latest tag only if release is not a pre-release - if [ "${{ github.event.release.prerelease }}" != "true" ]; then + else TAGS="${TAGS},freikin/dawarich:latest" fi @@ -67,6 +67,6 @@ jobs: file: ./docker/Dockerfile.dev push: true tags: ${{ steps.docker_meta.outputs.tags }} - platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6 + platforms: ${{ matrix.platform }} cache-from: type=local,src=/tmp/.buildx-cache cache-to: type=local,dest=/tmp/.buildx-cache From a52a3df4b9e26c6508d0e03dc1e1a2b806cfa3d3 Mon Sep 17 00:00:00 2001 From: Eugene Burmakin Date: Thu, 13 Feb 2025 19:09:06 +0100 Subject: [PATCH 13/18] Update build and push workflow --- .github/workflows/build_and_push.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build_and_push.yml b/.github/workflows/build_and_push.yml index c991e46f..80e5dab4 100644 --- a/.github/workflows/build_and_push.yml +++ b/.github/workflows/build_and_push.yml @@ -70,3 +70,5 @@ jobs: platforms: ${{ matrix.platform }} cache-from: type=local,src=/tmp/.buildx-cache cache-to: type=local,dest=/tmp/.buildx-cache + provenance: false + outputs: type=registry,push=true From 41243dda757f03789e2bb8cdc604c4910489a25b Mon Sep 17 00:00:00 2001 From: Evgenii Burmakin Date: Thu, 13 Feb 2025 20:12:14 +0100 Subject: [PATCH 14/18] Revert "Feature/parallel docker image builds" --- .github/workflows/build_and_push.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build_and_push.yml b/.github/workflows/build_and_push.yml index 80e5dab4..a0092f9f 100644 --- a/.github/workflows/build_and_push.yml +++ b/.github/workflows/build_and_push.yml @@ -11,11 +11,8 @@ on: types: [created] jobs: - build-and-push: + build-and-push-docker: runs-on: ubuntu-22.04 - strategy: - matrix: - platform: ["linux/amd64", "linux/arm64", "linux/arm/v7", "linux/arm/v6"] steps: - name: Checkout code uses: actions/checkout@v4 @@ -54,7 +51,10 @@ jobs: # Add :rc tag for pre-releases if [ "${{ github.event.release.prerelease }}" = "true" ]; then TAGS="${TAGS},freikin/dawarich:rc" - else + fi + + # Add :latest tag only if release is not a pre-release + if [ "${{ github.event.release.prerelease }}" != "true" ]; then TAGS="${TAGS},freikin/dawarich:latest" fi @@ -67,7 +67,7 @@ jobs: file: ./docker/Dockerfile.dev push: true tags: ${{ steps.docker_meta.outputs.tags }} - platforms: ${{ matrix.platform }} + platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6 cache-from: type=local,src=/tmp/.buildx-cache cache-to: type=local,dest=/tmp/.buildx-cache provenance: false From 3d01bead20a14af32dd55b31e0e3e02b3c0c923a Mon Sep 17 00:00:00 2001 From: Evgenii Burmakin Date: Thu, 13 Feb 2025 20:28:09 +0100 Subject: [PATCH 15/18] Revert "Revert "Add radius param of 10"" --- app/services/reverse_geocoding/places/fetch_data.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/reverse_geocoding/places/fetch_data.rb b/app/services/reverse_geocoding/places/fetch_data.rb index 9eec9de4..12186c9f 100644 --- a/app/services/reverse_geocoding/places/fetch_data.rb +++ b/app/services/reverse_geocoding/places/fetch_data.rb @@ -96,7 +96,7 @@ class ReverseGeocoding::Places::FetchData end def reverse_geocoded_places - data = Geocoder.search([place.latitude, place.longitude], limit: 10, distance_sort: true) + data = Geocoder.search([place.latitude, place.longitude], limit: 10, distance_sort: true, radius: 10) data.reject do |place| place.data['properties']['osm_value'].in?(IGNORED_OSM_VALUES) || From 51e589e17ffc834d6a22b10a836d21896e4b032a Mon Sep 17 00:00:00 2001 From: Eugene Burmakin Date: Thu, 13 Feb 2025 21:04:29 +0100 Subject: [PATCH 16/18] Implement map tiles usage tracking and chart in user settings. --- CHANGELOG.md | 1 + .../api/v1/maps/tile_usage_controller.rb | 2 +- app/controllers/settings/maps_controller.rb | 7 +++ app/javascript/controllers/maps_controller.js | 5 +- app/javascript/maps/tile_monitor.js | 10 +-- app/services/maps/tile_usage/track.rb | 23 ++++++- .../reverse_geocoding/places/fetch_data.rb | 8 ++- app/views/map/index.html.erb | 1 - app/views/settings/maps/index.html.erb | 63 +++++++++++-------- config/initializers/reddis.rb | 7 --- spec/requests/api/v1/maps/tile_usage_spec.rb | 7 +-- spec/services/maps/tile_usage/track_spec.rb | 17 ++++- 12 files changed, 94 insertions(+), 57 deletions(-) delete mode 100644 config/initializers/reddis.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fb1084b..c121fd47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ To set a custom tile URL, go to the user settings and set the `Maps` section to - Safe settings for user with default values. - In the user settings, you can now set a custom tile URL for the map. #429 #715 +- In the user map settings, you can now see a chart of map tiles usage. - If you have Prometheus exporter enabled, you can now see a `ruby_dawarich_map_tiles` metric in Prometheus, which shows the total number of map tiles loaded. Example: ``` diff --git a/app/controllers/api/v1/maps/tile_usage_controller.rb b/app/controllers/api/v1/maps/tile_usage_controller.rb index 43f8f070..c22778e7 100644 --- a/app/controllers/api/v1/maps/tile_usage_controller.rb +++ b/app/controllers/api/v1/maps/tile_usage_controller.rb @@ -2,7 +2,7 @@ class Api::V1::Maps::TileUsageController < ApiController def create - Maps::TileUsage::Track.new(tile_usage_params[:count].to_i).call + Maps::TileUsage::Track.new(current_api_user.id, tile_usage_params[:count].to_i).call head :ok end diff --git a/app/controllers/settings/maps_controller.rb b/app/controllers/settings/maps_controller.rb index 59beb04d..58e2fef6 100644 --- a/app/controllers/settings/maps_controller.rb +++ b/app/controllers/settings/maps_controller.rb @@ -5,6 +5,13 @@ class Settings::MapsController < ApplicationController def index @maps = current_user.safe_settings.maps + + @tile_usage = 7.days.ago.to_date.upto(Time.zone.today).map do |date| + [ + date.to_s, + Rails.cache.read("dawarich_map_tiles_usage:#{current_user.id}:#{date}") || 0 + ] + end end def update diff --git a/app/javascript/controllers/maps_controller.js b/app/javascript/controllers/maps_controller.js index 53b39c20..d2f59dbb 100644 --- a/app/javascript/controllers/maps_controller.js +++ b/app/javascript/controllers/maps_controller.js @@ -34,9 +34,6 @@ import { TileMonitor } from "../maps/tile_monitor"; export default class extends Controller { static targets = ["container"]; - static values = { - monitoringEnabled: Boolean - } settingsButtonAdded = false; layerControl = null; @@ -249,7 +246,7 @@ export default class extends Controller { } // Initialize tile monitor - this.tileMonitor = new TileMonitor(this.monitoringEnabledValue, this.apiKey); + this.tileMonitor = new TileMonitor(this.apiKey); // Add tile load event handlers to each base layer Object.entries(this.baseMaps()).forEach(([name, layer]) => { diff --git a/app/javascript/maps/tile_monitor.js b/app/javascript/maps/tile_monitor.js index 3a4ff36e..0a1edc60 100644 --- a/app/javascript/maps/tile_monitor.js +++ b/app/javascript/maps/tile_monitor.js @@ -1,15 +1,11 @@ export class TileMonitor { - constructor(monitoringEnabled, apiKey) { - this.monitoringEnabled = monitoringEnabled; + constructor(apiKey) { this.apiKey = apiKey; this.tileQueue = 0; this.tileUpdateInterval = null; } startMonitoring() { - // Only start the interval if monitoring is enabled - if (!this.monitoringEnabled) return; - // Clear any existing interval if (this.tileUpdateInterval) { clearInterval(this.tileUpdateInterval); @@ -29,13 +25,11 @@ export class TileMonitor { } recordTileLoad() { - if (!this.monitoringEnabled) return; this.tileQueue += 1; } sendTileUsage() { - // Don't send if monitoring is disabled or queue is empty - if (!this.monitoringEnabled || this.tileQueue === 0) return; + if (this.tileQueue === 0) return; const currentCount = this.tileQueue; console.log('Sending tile usage batch:', currentCount); diff --git a/app/services/maps/tile_usage/track.rb b/app/services/maps/tile_usage/track.rb index 0affd754..a2ec819d 100644 --- a/app/services/maps/tile_usage/track.rb +++ b/app/services/maps/tile_usage/track.rb @@ -1,11 +1,23 @@ # frozen_string_literal: true class Maps::TileUsage::Track - def initialize(count = 1) + def initialize(user_id, count = 1) + @user_id = user_id @count = count end def call + report_to_prometheus + report_to_cache + rescue StandardError => e + Rails.logger.error("Failed to send tile usage metric: #{e.message}") + end + + private + + def report_to_prometheus + return unless DawarichSettings.prometheus_exporter_enabled? + metric_data = { type: 'counter', name: 'dawarich_map_tiles_usage', @@ -13,7 +25,12 @@ class Maps::TileUsage::Track } PrometheusExporter::Client.default.send_json(metric_data) - rescue StandardError => e - Rails.logger.error("Failed to send tile usage metric: #{e.message}") + end + + def report_to_cache + today_key = "dawarich_map_tiles_usage:#{@user_id}:#{Time.zone.today}" + + current_value = (Rails.cache.read(today_key) || 0).to_i + Rails.cache.write(today_key, current_value + @count, expires_in: 7.days) end end diff --git a/app/services/reverse_geocoding/places/fetch_data.rb b/app/services/reverse_geocoding/places/fetch_data.rb index 12186c9f..9b691d36 100644 --- a/app/services/reverse_geocoding/places/fetch_data.rb +++ b/app/services/reverse_geocoding/places/fetch_data.rb @@ -96,7 +96,13 @@ class ReverseGeocoding::Places::FetchData end def reverse_geocoded_places - data = Geocoder.search([place.latitude, place.longitude], limit: 10, distance_sort: true, radius: 10) + data = Geocoder.search( + [place.latitude, place.longitude], + limit: 10, + distance_sort: true, + radius: 1, + units: DISTANCE_UNITS + ) data.reject do |place| place.data['properties']['osm_value'].in?(IGNORED_OSM_VALUES) || diff --git a/app/views/map/index.html.erb b/app/views/map/index.html.erb index 511f12a7..9fa4a0fe 100644 --- a/app/views/map/index.html.erb +++ b/app/views/map/index.html.erb @@ -53,7 +53,6 @@ data-coordinates="<%= @coordinates %>" data-distance="<%= @distance %>" data-points_number="<%= @points_number %>" - data-maps-monitoring-enabled-value="<%= DawarichSettings.prometheus_exporter_enabled? %>" data-timezone="<%= Rails.configuration.time_zone %>">
diff --git a/app/views/settings/maps/index.html.erb b/app/views/settings/maps/index.html.erb index 2e50950d..e80d875a 100644 --- a/app/views/settings/maps/index.html.erb +++ b/app/views/settings/maps/index.html.erb @@ -22,35 +22,46 @@ Please remember, that using a custom tile URL may result in extra costs. Check your map tile provider's terms of service for more information.
-
- <%= form_for :maps, - url: settings_maps_path, - method: :patch, - autocomplete: "off", - data: { turbo_method: :patch, turbo: false }, - class: "lg:col-span-1" do |f| %> -
- <%= f.label :name %> - <%= f.text_field :name, value: @maps['name'], placeholder: 'Example: OpenStreetMap', class: "input input-bordered" %> -
+
+
+ <%= form_for :maps, + url: settings_maps_path, + method: :patch, + autocomplete: "off", + data: { turbo_method: :patch, turbo: false } do |f| %> +
+ <%= f.label :name %> + <%= f.text_field :name, value: @maps['name'], placeholder: 'Example: OpenStreetMap', class: "input input-bordered" %> +
-
- <%= f.label :url, 'URL' %> - <%= f.text_field :url, - value: @maps['url'], - autocomplete: "off", - placeholder: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', - class: "input input-bordered", - data: { - map_preview_target: "urlInput", - action: "input->map-preview#updatePreview" - } %> -
+
+ <%= f.label :url, 'URL' %> + <%= f.text_field :url, + value: @maps['url'], + autocomplete: "off", + placeholder: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + class: "input input-bordered", + data: { + map_preview_target: "urlInput", + action: "input->map-preview#updatePreview" + } %> +
- <%= f.submit 'Save', class: "btn btn-primary", data: { map_preview_target: "saveButton" } %> - <% end %> + <%= f.submit 'Save', class: "btn btn-primary", data: { map_preview_target: "saveButton" } %> + <% end %> -
+

Tile usage

+ + <%= line_chart( + @tile_usage, + height: '200px', + xtitle: 'Days', + ytitle: 'Tiles', + suffix: ' tiles loaded' + ) %> +
+ +
"Bearer #{user.api_key}" } - expect(Maps::TileUsage::Track).to have_received(:new).with(tile_count) + expect(Maps::TileUsage::Track).to have_received(:new).with(user.id, tile_count) expect(track_service).to have_received(:call) expect(response).to have_http_status(:ok) end diff --git a/spec/services/maps/tile_usage/track_spec.rb b/spec/services/maps/tile_usage/track_spec.rb index 896810eb..678f60b1 100644 --- a/spec/services/maps/tile_usage/track_spec.rb +++ b/spec/services/maps/tile_usage/track_spec.rb @@ -5,16 +5,19 @@ require 'prometheus_exporter/client' RSpec.describe Maps::TileUsage::Track do describe '#call' do - subject(:track) { described_class.new(tile_count).call } + subject(:track) { described_class.new(user_id, tile_count).call } + let(:user_id) { 1 } let(:tile_count) { 5 } let(:prometheus_client) { instance_double(PrometheusExporter::Client) } before do allow(PrometheusExporter::Client).to receive(:default).and_return(prometheus_client) + allow(prometheus_client).to receive(:send_json) + allow(DawarichSettings).to receive(:prometheus_exporter_enabled?).and_return(true) end - it 'tracks tile usage' do + it 'tracks tile usage in prometheus' do expect(prometheus_client).to receive(:send_json).with( { type: 'counter', @@ -25,5 +28,15 @@ RSpec.describe Maps::TileUsage::Track do track end + + it 'tracks tile usage in cache' do + expect(Rails.cache).to receive(:write).with( + "dawarich_map_tiles_usage:#{user_id}:#{Time.zone.today}", + tile_count, + expires_in: 7.days + ) + + track + end end end From 491188d65d0cfde0f9074a5137f3f7c3d819aa96 Mon Sep 17 00:00:00 2001 From: Eugene Burmakin Date: Thu, 13 Feb 2025 21:17:29 +0100 Subject: [PATCH 17/18] Update changelog --- .github/workflows/build_and_push.yml | 2 -- CHANGELOG.md | 6 ++++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build_and_push.yml b/.github/workflows/build_and_push.yml index a0092f9f..49580227 100644 --- a/.github/workflows/build_and_push.yml +++ b/.github/workflows/build_and_push.yml @@ -70,5 +70,3 @@ jobs: platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6 cache-from: type=local,src=/tmp/.buildx-cache cache-to: type=local,dest=/tmp/.buildx-cache - provenance: false - outputs: type=registry,push=true diff --git a/CHANGELOG.md b/CHANGELOG.md index c121fd47..30c3a8fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ To set a custom tile URL, go to the user settings and set the `Maps` section to ### Added - Safe settings for user with default values. +- Nominatim API is now supported as a reverse geocoding provider. - In the user settings, you can now set a custom tile URL for the map. #429 #715 - In the user map settings, you can now see a chart of map tiles usage. - If you have Prometheus exporter enabled, you can now see a `ruby_dawarich_map_tiles` metric in Prometheus, which shows the total number of map tiles loaded. Example: @@ -28,6 +29,11 @@ ruby_dawarich_map_tiles_usage 99 ### Fixed - Speed on the Points page is now being displayed in kilometers per hour. #700 +- Fog of war displacement #774 + +### Reverted + +- #748 # 0.24.0 - 2025-02-10 From 674b057d37eab4b478017a9ffb9047f1725c3221 Mon Sep 17 00:00:00 2001 From: Eugene Burmakin Date: Thu, 13 Feb 2025 21:17:55 +0100 Subject: [PATCH 18/18] Update date in changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30c3a8fe..0aab7b37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ 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/). -# 0.24.1 - 2025-02-11 +# 0.24.1 - 2025-02-13 ## Custom map tiles