]> git.openstreetmap.org Git - rails.git/commitdiff
Add profile location
authornertc <davidtsiklauri7@gmail.com>
Fri, 25 Apr 2025 14:31:39 +0000 (18:31 +0400)
committernertc <davidtsiklauri7@gmail.com>
Fri, 25 Apr 2025 14:31:39 +0000 (18:31 +0400)
12 files changed:
app/assets/javascripts/home_location_name-endpoint.js [new file with mode: 0644]
app/assets/javascripts/user.js
app/controllers/profiles_controller.rb
app/controllers/searches/nominatim_reverse_queries_controller.rb
app/models/user.rb
app/views/profiles/edit.html.erb
app/views/users/show.html.erb
config/locales/en.yml
db/migrate/20241030090336_add_user_location_name.rb [new file with mode: 0644]
db/structure.sql
test/controllers/searches_controller_test.rb
test/system/user_location_change_test.rb [new file with mode: 0644]

diff --git a/app/assets/javascripts/home_location_name-endpoint.js b/app/assets/javascripts/home_location_name-endpoint.js
new file mode 100644 (file)
index 0000000..f1b5944
--- /dev/null
@@ -0,0 +1,65 @@
+OSM.HomeLocationNameGeocoder = function Endpoint(latInput, lonInput, locationNameInput) {
+  const endpoint = {
+    autofill: true,
+    countryName: locationNameInput.val().trim()
+  };
+
+  let requestController = null;
+
+  endpoint.updateHomeLocationName = function (
+    updateInput = true,
+    lat = latInput.val().trim(),
+    lon = lonInput.val().trim(),
+    successFn
+  ) {
+    if (!lat || !lon || !endpoint.autofill) {
+      return;
+    }
+
+    const geocodeUrl = "/search/nominatim_reverse_query",
+          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 (requestController) {
+      requestController.abort();
+    }
+    const currentRequestController = new AbortController();
+    requestController = currentRequestController;
+
+    fetch(geocodeUrl, {
+      method: "POST",
+      body: params,
+      signal: requestController.signal,
+      headers: { accept: "application/json" }
+    })
+      .then((response) => response.json())
+      .then((data) => {
+        const country = data.length ? data[0].name : "";
+
+        if (updateInput) {
+          $("#home_location_name").val(country);
+        } else if (endpoint.countryName !== country) {
+          endpoint.autofill = false;
+        }
+        endpoint.countryName = country;
+        requestController = null;
+
+        if (successFn) {
+          successFn();
+        }
+      })
+      .catch(() => {
+        if (currentRequestController === requestController) {
+          requestController = null;
+        }
+      });
+  };
+
+  return endpoint;
+};
index 5f8a931e3956132583e8a9f52734b9621b698a70..028bf2074a7e25d662af0273fed496a083f21708 100644 (file)
@@ -1,4 +1,5 @@
 //= require leaflet.locate
+//= require ./home_location_name-endpoint
 
 (function () {
   $(document).on("change", "#user_all", function () {
@@ -8,7 +9,7 @@
 
 $(function () {
   const defaultHomeZoom = 12;
-  let map, marker, deleted_lat, deleted_lon;
+  let map, marker, deleted_lat, deleted_lon, deleted_home_name, homeLocationNameGeocoder, savedLat, savedLon;
 
   if ($("#map").length) {
     map = L.map("map", {
@@ -16,6 +17,10 @@ $(function () {
       zoomControl: false
     }).addLayer(new L.OSM.Mapnik());
 
+    savedLat = $("#home_lat").val();
+    savedLon = $("#home_lon").val();
+    homeLocationNameGeocoder = OSM.HomeLocationNameGeocoder($("#home_lat"), $("#home_lon"), $("#home_location_name"));
+
     const position = $("html").attr("dir") === "rtl" ? "topleft" : "topright";
 
     L.OSM.zoom({ position }).addTo(map);
@@ -48,9 +53,8 @@ $(function () {
         $("#home_lat").val(lat);
         $("#home_lon").val(lon);
 
-        deleted_lat = null;
-        deleted_lon = null;
-        respondToHomeUpdate();
+        clearDeletedText();
+        respondToHomeLatLonUpdate();
       }).on("moveend", function () {
         const lat = $("#home_lat").val().trim(),
               lon = $("#home_lon").val().trim();
@@ -68,9 +72,15 @@ $(function () {
       });
 
       $("#home_lat, #home_lon").on("input", function () {
-        deleted_lat = null;
-        deleted_lon = null;
-        respondToHomeUpdate();
+        clearDeletedText();
+        respondToHomeLatLonUpdate();
+      });
+
+      $("#home_location_name").on("input", function () {
+        homeLocationNameGeocoder.autofill = false;
+        clearDeletedText();
+
+        respondToHomeLatLonUpdate(false);
       });
 
       $("#home_show").click(function () {
@@ -82,21 +92,25 @@ $(function () {
 
       $("#home_delete").click(function () {
         const lat = $("#home_lat").val(),
-              lon = $("#home_lon").val();
+              lon = $("#home_lon").val(),
+              locationName = $("#home_location_name").val();
 
-        $("#home_lat, #home_lon").val("");
+        $("#home_lat, #home_lon, #home_location_name").val("");
         deleted_lat = lat;
         deleted_lon = lon;
-        respondToHomeUpdate();
+        deleted_home_name = locationName;
+
+        respondToHomeLatLonUpdate(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();
+        $("#home_location_name").val(deleted_home_name);
+        clearDeletedText();
+
+        respondToHomeLatLonUpdate(false);
         $("#home_delete").trigger("focus");
       });
     } else {
@@ -110,14 +124,26 @@ $(function () {
     }
   }
 
-  function respondToHomeUpdate() {
+  function respondToHomeLatLonUpdate(updateLocationName = true) {
     const lat = $("#home_lat").val().trim(),
-          lon = $("#home_lon").val().trim();
+          lon = $("#home_lon").val().trim(),
+          locationName = $("#home_location_name").val().trim();
     let location;
 
     try {
       if (lat && lon) {
         location = L.latLng(lat, lon);
+        if (updateLocationName) {
+          if (savedLat && savedLon && $("#home_location_name").val().trim()) {
+            homeLocationNameGeocoder.updateHomeLocationName(false, savedLat, savedLon, () => {
+              savedLat = savedLon = null;
+              homeLocationNameGeocoder.updateHomeLocationName();
+            });
+          } else {
+            savedLat = savedLon = null;
+            homeLocationNameGeocoder.updateHomeLocationName();
+          }
+        }
       }
       $("#home_lat, #home_lon").removeClass("is-invalid");
     } catch (error) {
@@ -127,8 +153,11 @@ $(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) || deleted_home_name)
+    ));
     if (location) {
       marker.setLatLng([lat, lon]);
       marker.addTo(map);
@@ -155,6 +184,12 @@ $(function () {
     }
   }
 
+  function clearDeletedText() {
+    deleted_lat = null;
+    deleted_lon = null;
+    deleted_home_name = null;
+  }
+
   updateAuthUID();
 
   $("select#user_auth_provider").on("change", updateAuthUID);
index 4005176ce192c529d8f4f535e57ada04569f1622..9d050bd75dac204683106a3b66683cf2b7d3a2d0 100644 (file)
@@ -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.home_location_name = params[:user][:home_location_name]
 
     if current_user.save
       flash[:notice] = t ".success"
index c0fe8e6c90057666ccdbea3e1ee04b06aae6654b..c2eced4e560841fa884a0f125ea34e40d0fa0af5 100644 (file)
@@ -24,6 +24,11 @@ module Searches
                       :zoom => zoom,
                       :name => description,
                       :type => object_type, :id => object_id)
+
+        respond_to do |format|
+          format.html
+          format.json { render :json => @results }
+        end
       end
     rescue StandardError => e
       host = URI(Settings.nominatim_url).host
index 695f29ed8d3f2713b5e89ee632f31bc478e81228..27e42e665a7cec50d0320fa3d633ff55f860b017 100644 (file)
@@ -12,6 +12,7 @@
 #  home_lat             :float
 #  home_lon             :float
 #  home_zoom            :integer          default(3)
+#  home_location_name   :string
 #  pass_salt            :string
 #  email_valid          :boolean          default(FALSE), not null
 #  new_email            :string
index ac76b4d2d532711fc0f39e09e27c35b16ee70b1c..a7dd437e5a2e924001bcd1854e46686a1e0df96d 100644 (file)
@@ -52,6 +52,7 @@
         <button type="button" id="home_undelete" class="btn btn-outline-primary" hidden><%= t ".undelete" %></button>
       </div>
     </div>
+    <%= f.text_field :home_location_name, :wrapper_class => "my-2 col-sm-4 pe-3", :class => "mt-auto", :id => "home_location_name" %>
     <div class="form-check">
       <input class="form-check-input" type="checkbox" name="updatehome" value="1" <% unless current_user.home_location? %> checked <% end %> id="updatehome" />
       <label class="form-check-label" for="updatehome"><%= t ".update home location on click" %></label>
index da6ddfb9deff2e9998cdc0eaa156e32426df7c0a..ace1d06a4afa537201acc1b71d420ba4dc718794 100644 (file)
       <div class='text-body-secondary'>
         <small>
           <dl class="list-inline">
+            <% if @user.home_location_name&.strip.present? %>
+              <dt class="list-inline-item m-0 align-text-bottom">
+                <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-geo-alt-fill" viewBox="0 0 16 16">
+                  <path d="M8 16s6-5.686 6-10A6 6 0 0 0 2 6c0 4.314 6 10 6 10m0-7a3 3 0 1 1 0-6 3 3 0 0 1 0 6">
+                    <title><%= t ".home location" %></title>
+                  </path>
+                </svg>
+              </dt>
+              <dd class="list-inline-item"><%= @user.home_location_name %></dd>
+            <% end %>
             <dt class="list-inline-item m-0"><%= t ".mapper since" %></dt>
             <dd class="list-inline-item"><%= l @user.created_at.to_date, :format => :long %></dd>
             <dt class="list-inline-item m-0"><%= t ".last map edit" %></dt>
index aa0d99fb798a83ba491af88b748697924acbd828..7774a203a3cf73f34033c0e61b4489abfa67dd68 100644 (file)
@@ -2891,6 +2891,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 (file)
index 0000000..a1874a4
--- /dev/null
@@ -0,0 +1,5 @@
+class AddUserLocationName < ActiveRecord::Migration[7.2]
+  def change
+    add_column :users, :home_location_name, :string
+  end
+end
index 48036fd8eeab3a530d1b1a22cc7f3398fe140be6..5e5b92dc5cdd2874cb0c0a94058bedf3bd4ac071 100644 (file)
@@ -1519,7 +1519,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,
+    home_location_name character varying
 );
 
 
@@ -3457,6 +3458,7 @@ INSERT INTO "schema_migrations" (version) VALUES
 ('20250121191749'),
 ('20250105154621'),
 ('20250104140952'),
+('20241030090336'),
 ('20241023004427'),
 ('20241022141247'),
 ('20240913171951'),
index bb625c5218a7af2c34a545779b0d863c87cb1ceb..d9e4d248ca63790df1ea495948aa2760110eecee 100644 (file)
@@ -311,6 +311,21 @@ class SearchesControllerTest < ActionDispatch::IntegrationTest
     search_check "foo bar baz", %w[nominatim]
   end
 
+  ##
+  # Test the nominatim reverse JSON search
+  def test_search_osm_nominatim_reverse_json
+    with_http_stubs "nominatim" do
+      post search_nominatim_reverse_query_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 search_nominatim_reverse_query_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 search_nominatim_reverse_query_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)
@@ -350,4 +365,11 @@ class SearchesControllerTest < ActionDispatch::IntegrationTest
     assert_template :layout => "xhr"
     assert_equal sources, assigns(:sources).pluck(:name)
   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 (file)
index 0000000..5524c98
--- /dev/null
@@ -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 "Home location name", :with => "Test Location"
+    click_on "Update Profile"
+
+    visit user_path(user)
+    assert_text "Test Location"
+  end
+end