From f8c5cb47014e588952ac0dcbc528b5b8ff793d06 Mon Sep 17 00:00:00 2001 From: nertc Date: Tue, 4 Mar 2025 15:57:04 +0400 Subject: [PATCH] Add profile location --- app/assets/javascripts/user.js | 118 +++++++++++++++--- app/controllers/geocoder_controller.rb | 6 +- app/controllers/profiles_controller.rb | 1 + app/models/user.rb | 1 + app/views/profiles/edit.html.erb | 1 + app/views/users/show.html.erb | 10 ++ config/locales/en.yml | 1 + .../20241030090336_add_user_location_name.rb | 5 + db/structure.sql | 4 +- test/controllers/geocoder_controller_test.rb | 22 ++++ test/system/user_location_change_test.rb | 22 ++++ 11 files changed, 174 insertions(+), 17 deletions(-) create mode 100644 db/migrate/20241030090336_add_user_location_name.rb create mode 100644 test/system/user_location_change_test.rb diff --git a/app/assets/javascripts/user.js b/app/assets/javascripts/user.js index 2af9e18cb..50987b18b 100644 --- a/app/assets/javascripts/user.js +++ b/app/assets/javascripts/user.js @@ -8,9 +8,19 @@ $(document).ready(function () { const defaultHomeZoom = 12; - let map, marker, deleted_lat, deleted_lon; + let map, marker, deleted_lat, deleted_lon, savedLat, savedLon, locationInput; if ($("#map").length) { + savedLat = $("#home_lat").val(); + savedLon = $("#home_lon").val(); + locationInput = { + dirty: false, + requestController: null, + countryName: $("#location_name").val().trim(), + deletedText: null, + checkLocation: savedLat && savedLon && $("#location_name").val().trim() + }; + map = L.map("map", { attributionControl: false, zoomControl: false @@ -69,8 +79,7 @@ $(document).ready(function () { $("#home_lat").val(lat); $("#home_lon").val(lon); - deleted_lat = null; - deleted_lon = null; + clearDeletedText(); respondToHomeUpdate(); }).on("moveend", function () { const lat = $("#home_lat").val().trim(), @@ -89,11 +98,17 @@ $(document).ready(function () { }); $("#home_lat, #home_lon").on("input", function () { - deleted_lat = null; - deleted_lon = null; + clearDeletedText(); respondToHomeUpdate(); }); + $("#location_name").on("input", function () { + locationInput.dirty = true; + clearDeletedText(); + + respondToHomeUpdate(false); + }); + $("#home_show").click(function () { const lat = $("#home_lat").val(), lon = $("#home_lon").val(); @@ -103,21 +118,25 @@ $(document).ready(function () { $("#home_delete").click(function () { const lat = $("#home_lat").val(), - lon = $("#home_lon").val(); + lon = $("#home_lon").val(), + locationName = $("#location_name").val(); - $("#home_lat, #home_lon").val(""); + $("#home_lat, #home_lon, #location_name").val(""); deleted_lat = lat; deleted_lon = lon; - respondToHomeUpdate(); + locationInput.deletedText = locationName; + + respondToHomeUpdate(false); $("#home_undelete").trigger("focus"); }); $("#home_undelete").click(function () { $("#home_lat").val(deleted_lat); $("#home_lon").val(deleted_lon); - deleted_lat = null; - deleted_lon = null; - respondToHomeUpdate(); + $("#location_name").val(locationInput.deletedText); + clearDeletedText(); + + respondToHomeUpdate(false); $("#home_delete").trigger("focus"); }); } else { @@ -131,14 +150,22 @@ $(document).ready(function () { } } - function respondToHomeUpdate() { + function respondToHomeUpdate(updateLocationName = true) { const lat = $("#home_lat").val().trim(), - lon = $("#home_lon").val().trim(); + lon = $("#home_lon").val().trim(), + locationName = $("#location_name").val().trim(); let location; try { if (lat && lon) { location = L.latLng(lat, lon); + if (updateLocationName) { + if (locationInput.checkLocation) { + updateHomeLocation(false, savedLat, savedLon, updateHomeLocation); + } else { + updateHomeLocation(); + } + } } $("#home_lat, #home_lon").removeClass("is-invalid"); } catch (error) { @@ -148,8 +175,11 @@ $(document).ready(function () { $("#home_message").toggleClass("invisible", Boolean(location)); $("#home_show").prop("hidden", !location); - $("#home_delete").prop("hidden", !location); - $("#home_undelete").prop("hidden", !(!location && deleted_lat && deleted_lon)); + $("#home_delete").prop("hidden", !location && !locationName); + $("#home_undelete").prop("hidden", !( + (!location || !locationName) && + ((deleted_lat && deleted_lon) || locationInput.deletedText) + )); if (location) { marker.setLatLng([lat, lon]); marker.addTo(map); @@ -176,6 +206,64 @@ $(document).ready(function () { } } + function updateHomeLocation(updateInput = true, lat = $("#home_lat").val().trim(), lon = $("#home_lon").val().trim(), successFn) { + if (!lat || !lon || locationInput.dirty) { + return; + } + + const geocodeUrl = "/geocoder/search_osm_nominatim_reverse", + csrf_param = $("meta[name=csrf-param]").attr("content"), + csrf_token = $("meta[name=csrf-token]").attr("content"), + params = new URLSearchParams({ + lat, + lon, + zoom: 3 + }); + params.set(csrf_param, csrf_token); + + if (locationInput.requestController) { + locationInput.requestController.abort(); + } + + const requestController = new AbortController(); + locationInput.requestController = requestController; + fetch(geocodeUrl, { + method: "POST", + body: params, + signal: locationInput.requestController.signal, + headers: { accept: "application/json" } + }) + .then(response => response.json()) + .then((data) => { + const country = data.length ? data[0].name : ""; + + if (updateInput) { + $("#location_name").val(country); + } else { + locationInput.checkLocation = false; + if (locationInput.countryName !== country) { + locationInput.dirty = true; + } + } + locationInput.countryName = country; + locationInput.requestController = null; + + if (successFn) { + successFn(); + } + }).catch(() => { + if (location.requestController === requestController) { + locationInput.requestController = null; + } + }); + } + + function clearDeletedText() { + deleted_lat = null; + deleted_lon = null; + locationInput.deletedText = null; + } + updateAuthUID(); $("select#user_auth_provider").on("change", updateAuthUID); diff --git a/app/controllers/geocoder_controller.rb b/app/controllers/geocoder_controller.rb index 85b4476f6..95315ef02 100644 --- a/app/controllers/geocoder_controller.rb +++ b/app/controllers/geocoder_controller.rb @@ -148,7 +148,11 @@ class GeocoderController < ApplicationController :type => object_type, :id => object_id) end - render :action => "results" + respond_to do |format| + format.html { render :action => "results" } + format.json { render :json => @results } + format.any { render :action => "results" } + end rescue StandardError => e host = URI(Settings.nominatim_url).host @error = "Error contacting #{host}: #{e}" diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb index 4005176ce..99d9d0e13 100644 --- a/app/controllers/profiles_controller.rb +++ b/app/controllers/profiles_controller.rb @@ -31,6 +31,7 @@ class ProfilesController < ApplicationController current_user.home_lat = params[:user][:home_lat] current_user.home_lon = params[:user][:home_lon] + current_user.location_name = params[:user][:location_name] if current_user.save flash[:notice] = t ".success" diff --git a/app/models/user.rb b/app/models/user.rb index 8d061e26b..f3c19e752 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -12,6 +12,7 @@ # home_lat :float # home_lon :float # home_zoom :integer default(3) +# location_name :string # pass_salt :string # email_valid :boolean default(FALSE), not null # new_email :string diff --git a/app/views/profiles/edit.html.erb b/app/views/profiles/edit.html.erb index ac76b4d2d..373ae5290 100644 --- a/app/views/profiles/edit.html.erb +++ b/app/views/profiles/edit.html.erb @@ -52,6 +52,7 @@ + <%= f.text_field :location_name, :wrapper_class => "my-2 col-sm-4 pe-3", :class => "mt-auto", :id => "location_name" %>
checked <% end %> id="updatehome" /> diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb index 30245aded..0e6dbef82 100644 --- a/app/views/users/show.html.erb +++ b/app/views/users/show.html.erb @@ -139,6 +139,16 @@
+ <% if @user.location_name && @user.location_name.strip.present? %> +
+ + + <%= t ".home location" %> + + +
+
<%= @user.location_name %>
+ <% end %>
<%= t ".mapper since" %>
<%= l @user.created_at.to_date, :format => :long %>
<%= t ".last map edit" %>
diff --git a/config/locales/en.yml b/config/locales/en.yml index 0deb199a3..ff0ed606d 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -2828,6 +2828,7 @@ en: follow: Follow mapper since: "Mapper since:" last map edit: "Last map edit:" + home location: "Home location" no activity yet: "No activity yet" uid: "User id:" ct status: "Contributor terms:" diff --git a/db/migrate/20241030090336_add_user_location_name.rb b/db/migrate/20241030090336_add_user_location_name.rb new file mode 100644 index 000000000..3cace38c8 --- /dev/null +++ b/db/migrate/20241030090336_add_user_location_name.rb @@ -0,0 +1,5 @@ +class AddUserLocationName < ActiveRecord::Migration[7.2] + def change + add_column :users, :location_name, :string + end +end diff --git a/db/structure.sql b/db/structure.sql index d23c2d748..bb1f7c452 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -1512,7 +1512,8 @@ CREATE TABLE public.users ( tou_agreed timestamp without time zone, diary_comments_count integer DEFAULT 0, note_comments_count integer DEFAULT 0, - creation_address inet + creation_address inet, + location_name character varying ); @@ -3433,6 +3434,7 @@ INSERT INTO "schema_migrations" (version) VALUES ('20250121191749'), ('20250105154621'), ('20250104140952'), +('20241030090336'), ('20241023004427'), ('20241022141247'), ('20240913171951'), diff --git a/test/controllers/geocoder_controller_test.rb b/test/controllers/geocoder_controller_test.rb index ac9905589..24a995046 100644 --- a/test/controllers/geocoder_controller_test.rb +++ b/test/controllers/geocoder_controller_test.rb @@ -420,6 +420,21 @@ class GeocoderControllerTest < ActionDispatch::IntegrationTest end end + ## + # Test the nominatim reverse JSON search + def test_search_osm_nominatim_reverse_json + with_http_stubs "nominatim" do + post geocoder_search_osm_nominatim_reverse_path(:lat => 51.7632, :lon => -0.0076, :zoom => 15, :format => "json"), :xhr => true + result_name_check_json("Broxbourne, Hertfordshire, East of England, England, United Kingdom") + + post geocoder_search_osm_nominatim_reverse_path(:lat => 51.7632, :lon => -0.0076, :zoom => 17, :format => "json"), :xhr => true + result_name_check_json("Dinant Link Road, Broxbourne, Hertfordshire, East of England, England, EN11 8HX, United Kingdom") + + post geocoder_search_osm_nominatim_reverse_path(:lat => 13.7709, :lon => 100.50507, :zoom => 19, :format => "json"), :xhr => true + result_name_check_json("MM Steak&Grill, ถนนศรีอยุธยา, บางขุนพรหม, กรุงเทพมหานคร, เขตดุสิต, กรุงเทพมหานคร, 10300, ประเทศไทย") + end + end + private def latlon_check(query, lat, lon) @@ -484,4 +499,11 @@ class GeocoderControllerTest < ActionDispatch::IntegrationTest assert_template :layout => nil assert_select ".alert.alert-danger", error end + + def result_name_check_json(name) + assert_response :success + js = ActiveSupport::JSON.decode(@response.body) + assert_not_nil js + assert_equal name, js[0]["name"] + end end diff --git a/test/system/user_location_change_test.rb b/test/system/user_location_change_test.rb new file mode 100644 index 000000000..9cf5f879e --- /dev/null +++ b/test/system/user_location_change_test.rb @@ -0,0 +1,22 @@ +require "application_system_test_case" + +class UserLocationChangeTest < ApplicationSystemTestCase + def setup + stub_request(:get, /.*gravatar.com.*d=404/).to_return(:status => 404) + end + + test "User can change their location" do + user = create(:user) + sign_in_as(user) + + visit user_path(user) + assert_no_selector ".bi.bi-geo-alt-fill" + + visit edit_profile_path + fill_in "Location name", :with => "Test Location" + click_on "Update Profile" + + visit user_path(user) + assert_text "Test Location" + end +end