]> git.openstreetmap.org Git - rails.git/commitdiff
Merge pull request #5421 from tomhughes/ecma-version
authorAndy Allan <git@gravitystorm.co.uk>
Mon, 20 Jan 2025 17:54:02 +0000 (17:54 +0000)
committerGitHub <noreply@github.com>
Mon, 20 Jan 2025 17:54:02 +0000 (17:54 +0000)
Bump allowed ECMAScript version to 2021 (ES12)

55 files changed:
.rubocop_todo.yml
app/abilities/ability.rb
app/assets/javascripts/leaflet.map.js
app/assets/stylesheets/common.scss
app/controllers/changesets_controller.rb
app/controllers/diary_entries_controller.rb
app/controllers/follows_controller.rb [new file with mode: 0644]
app/controllers/friendships_controller.rb [deleted file]
app/controllers/traces/feeds_controller.rb [new file with mode: 0644]
app/controllers/traces_controller.rb
app/controllers/users/statuses_controller.rb [new file with mode: 0644]
app/controllers/users_controller.rb
app/helpers/changesets_helper.rb
app/mailers/user_mailer.rb
app/models/follow.rb [moved from app/models/friendship.rb with 69% similarity]
app/models/user.rb
app/views/accounts/edit.html.erb
app/views/api/messages/_message.json.jbuilder
app/views/api/messages/_message.xml.builder
app/views/dashboards/_contact.html.erb
app/views/dashboards/show.html.erb
app/views/follows/show.html.erb [new file with mode: 0644]
app/views/friendships/make_friend.html.erb [deleted file]
app/views/friendships/remove_friend.html.erb [deleted file]
app/views/traces/feeds/_description.html.erb [moved from app/views/traces/_description.html.erb with 100% similarity]
app/views/traces/feeds/show.rss.builder [moved from app/views/traces/georss.rss.builder with 88% similarity]
app/views/traces/index.html.erb
app/views/user_mailer/follow_notification.html.erb [new file with mode: 0644]
app/views/user_mailer/follow_notification.text.erb [new file with mode: 0644]
app/views/user_mailer/friendship_notification.html.erb [deleted file]
app/views/user_mailer/friendship_notification.text.erb [deleted file]
app/views/users/show.html.erb
config/layers.yml
config/locales/en.yml
config/routes.rb
config/settings.yml
db/migrate/20250105154621_validate_foreign_key_on_notes.rb [new file with mode: 0644]
db/structure.sql
test/controllers/api/messages_controller_test.rb
test/controllers/api/tracepoints_controller_test.rb
test/controllers/changesets_controller_test.rb
test/controllers/diary_entries_controller_test.rb
test/controllers/follows_controller_test.rb [new file with mode: 0644]
test/controllers/friendships_controller_test.rb [deleted file]
test/controllers/oauth2_authorized_applications_controller_test.rb
test/controllers/traces/feeds_controller_test.rb [new file with mode: 0644]
test/controllers/traces_controller_test.rb
test/controllers/users/statuses_controller_test.rb [new file with mode: 0644]
test/controllers/users_controller_test.rb
test/factories/follows.rb [new file with mode: 0644]
test/factories/friendships.rb [deleted file]
test/models/user_test.rb
test/system/dashboard_test.rb
test/system/follows_test.rb [new file with mode: 0644]
test/system/friendships_test.rb [deleted file]

index b3379b91a14243fec617620f973a8f5c3aaa8db7..fa15ea90a0d5a1ba00019d93f94bc52a157a7fda 100644 (file)
@@ -112,7 +112,6 @@ Rails/ActionControllerFlashBeforeRender:
   Exclude:
     - 'app/controllers/application_controller.rb'
     - 'app/controllers/confirmations_controller.rb'
-    - 'app/controllers/friendships_controller.rb'
     - 'app/controllers/issue_comments_controller.rb'
     - 'app/controllers/messages_controller.rb'
     - 'app/controllers/passwords_controller.rb'
@@ -142,7 +141,6 @@ Rails/InverseOf:
   Exclude:
     - 'app/models/changeset.rb'
     - 'app/models/diary_entry.rb'
-    - 'app/models/friendship.rb'
     - 'app/models/issue.rb'
     - 'app/models/message.rb'
     - 'app/models/note.rb'
index 05f4952ac21907e0c1fbdcd869f94f134449f726..3ba2ab7071cf763a5fe37b50e277aa7914ab1a47 100644 (file)
@@ -22,7 +22,7 @@ class Ability
       can [:create, :update], :password
       can :read, Redaction
       can [:create, :destroy], :session
-      can [:read, :data, :georss], Trace
+      can [:read, :data], Trace
       can [:read, :create, :suspended, :auth_success, :auth_failure], User
       can :read, UserBlock
     end
@@ -42,7 +42,7 @@ class Ability
         can [:create, :subscribe, :unsubscribe], DiaryEntry
         can :update, DiaryEntry, :user => user
         can [:create], DiaryComment
-        can [:make_friend, :remove_friend], Friendship
+        can [:show, :create, :destroy], Follow
         can [:read, :create, :mark, :unmute, :destroy], Message
         can [:close, :reopen], Note
         can [:read, :update], :preference
@@ -67,7 +67,8 @@ class Ability
           can [:hide, :unhide], [DiaryEntry, DiaryComment]
           can [:read, :resolve, :ignore, :reopen], Issue
           can :create, IssueComment
-          can [:set_status, :destroy], User
+
+          can [:update], :user_status
           can [:read, :update], :users_list
           can [:create, :destroy], UserRole
         end
index e0c2ee60b47fb0ddd5251ac520b3c8e234f8ebca..7929ea0ac7f33545f34c2a73de64150b1ed0f231 100644 (file)
@@ -32,6 +32,8 @@ L.OSM.Map = L.Map.extend({
           layerOptions.apikey = OSM[value];
         } else if (property === "leafletOsmId") {
           layerConstructor = L.OSM[value];
+        } else if (property === "leafletOsmDarkId" && OSM.isDarkMap() && L.OSM[value]) {
+          layerConstructor = L.OSM[value];
         } else {
           layerOptions[property] = value;
         }
@@ -386,6 +388,14 @@ L.extend(L.Icon.Default.prototype, {
   }
 });
 
+OSM.isDarkMap = function () {
+  var mapTheme = $("body").attr("data-map-theme");
+  if (mapTheme) return mapTheme === "dark";
+  var siteTheme = $("html").attr("data-bs-theme");
+  if (siteTheme) return siteTheme === "dark";
+  return window.matchMedia("(prefers-color-scheme: dark)").matches;
+};
+
 OSM.getUserIcon = function (url) {
   return L.icon({
     iconUrl: url || OSM.MARKER_RED,
index 9ce6aec3439542d7f213ab62faaee26222308b6d..098370689a0ec29215550527b4c08f0f58352a13 100644 (file)
@@ -9,7 +9,7 @@
 
 body {
   font-size: $typeheight;
-  --dark-mode-map-filter: brightness(.8);
+  --dark-mode-map-filter: none;
 }
 
 time[title] {
index 928f1c1ecf25724fa33fc9933eed9d49ded35bc4..aa9d81343a1c61764bb8637d3b4d7e7a6421a080 100644 (file)
@@ -55,7 +55,7 @@ class ChangesetsController < ApplicationController
       elsif @params[:bbox]
         changesets = conditions_bbox(changesets, BoundingBox.from_bbox_params(params))
       elsif @params[:friends] && current_user
-        changesets = changesets.where(:user => current_user.friends.identifiable)
+        changesets = changesets.where(:user => current_user.followings.identifiable)
       elsif @params[:nearby] && current_user
         changesets = changesets.where(:user => current_user.nearby)
       end
index 27099cd9cf9835558624088b34568b0fa9e587fa..94876e72a38b539bf2b21240cfa041f79f6a51c3 100644 (file)
@@ -28,8 +28,8 @@ class DiaryEntriesController < ApplicationController
       end
     elsif params[:friends]
       if current_user
-        @title = t ".title_friends"
-        entries = DiaryEntry.where(:user => current_user.friends)
+        @title = t ".title_followed"
+        entries = DiaryEntry.where(:user => current_user.followings)
       else
         require_user
         return
diff --git a/app/controllers/follows_controller.rb b/app/controllers/follows_controller.rb
new file mode 100644 (file)
index 0000000..a8da933
--- /dev/null
@@ -0,0 +1,51 @@
+class FollowsController < ApplicationController
+  include UserMethods
+
+  layout "site"
+
+  before_action :authorize_web
+  before_action :set_locale
+  before_action :check_database_readable
+
+  authorize_resource
+
+  before_action :check_database_writable
+  before_action :lookup_user
+
+  def show
+    @already_follows = current_user.follows?(@user)
+  end
+
+  def create
+    follow = Follow.new
+    follow.follower = current_user
+    follow.following = @user
+    if current_user.follows?(@user)
+      flash[:warning] = t ".already_followed", :name => @user.display_name
+    elsif current_user.follows.where(:created_at => Time.now.utc - 1.hour..).count >= current_user.max_follows_per_hour
+      flash[:error] = t ".limit_exceeded"
+    elsif follow.save
+      flash[:notice] = t ".success", :name => @user.display_name
+      UserMailer.follow_notification(follow).deliver_later
+    else
+      follow.add_error(t(".failed", :name => @user.display_name))
+    end
+
+    referer = safe_referer(params[:referer]) if params[:referer]
+
+    redirect_to referer || user_path
+  end
+
+  def destroy
+    if current_user.follows?(@user)
+      Follow.where(:follower => current_user, :following => @user).delete_all
+      flash[:notice] = t ".success", :name => @user.display_name
+    else
+      flash[:error] = t ".not_followed", :name => @user.display_name
+    end
+
+    referer = safe_referer(params[:referer]) if params[:referer]
+
+    redirect_to referer || user_path
+  end
+end
diff --git a/app/controllers/friendships_controller.rb b/app/controllers/friendships_controller.rb
deleted file mode 100644 (file)
index 8f0c1ad..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-class FriendshipsController < ApplicationController
-  include UserMethods
-
-  layout "site"
-
-  before_action :authorize_web
-  before_action :set_locale
-  before_action :check_database_readable
-
-  authorize_resource
-
-  before_action :check_database_writable, :only => [:make_friend, :remove_friend]
-  before_action :lookup_friend, :only => [:make_friend, :remove_friend]
-
-  def make_friend
-    if request.post?
-      friendship = Friendship.new
-      friendship.befriender = current_user
-      friendship.befriendee = @friend
-      if current_user.friends_with?(@friend)
-        flash[:warning] = t ".already_a_friend", :name => @friend.display_name
-      elsif current_user.friendships.where(:created_at => Time.now.utc - 1.hour..).count >= current_user.max_friends_per_hour
-        flash[:error] = t ".limit_exceeded"
-      elsif friendship.save
-        flash[:notice] = t ".success", :name => @friend.display_name
-        UserMailer.friendship_notification(friendship).deliver_later
-      else
-        friendship.add_error(t(".failed", :name => @friend.display_name))
-      end
-
-      referer = safe_referer(params[:referer]) if params[:referer]
-
-      redirect_to referer || user_path
-    end
-  end
-
-  def remove_friend
-    if request.post?
-      if current_user.friends_with?(@friend)
-        Friendship.where(:befriender => current_user, :befriendee => @friend).delete_all
-        flash[:notice] = t ".success", :name => @friend.display_name
-      else
-        flash[:error] = t ".not_a_friend", :name => @friend.display_name
-      end
-
-      referer = safe_referer(params[:referer]) if params[:referer]
-
-      redirect_to referer || user_path
-    end
-  end
-
-  private
-
-  ##
-  # ensure that there is a "friend" instance variable
-  def lookup_friend
-    @friend = User.active.find_by!(:display_name => params[:display_name])
-  rescue ActiveRecord::RecordNotFound
-    render_unknown_user params[:display_name]
-  end
-end
diff --git a/app/controllers/traces/feeds_controller.rb b/app/controllers/traces/feeds_controller.rb
new file mode 100644 (file)
index 0000000..293fbab
--- /dev/null
@@ -0,0 +1,20 @@
+module Traces
+  class FeedsController < ApplicationController
+    before_action :authorize_web
+    before_action :set_locale
+    before_action :check_database_readable
+
+    authorize_resource :class => Trace
+
+    def show
+      @traces = Trace.visible_to_all.visible
+
+      @traces = @traces.joins(:user).where(:users => { :display_name => params[:display_name] }) if params[:display_name]
+
+      @traces = @traces.tagged(params[:tag]) if params[:tag]
+      @traces = @traces.order("timestamp DESC")
+      @traces = @traces.limit(20)
+      @traces = @traces.includes(:user)
+    end
+  end
+end
index d723bac5b73b634f62d32e99ae6337f8e41c3916..14648dc9c37eea68d78bb9b255ede055cbca15c1 100644 (file)
@@ -2,7 +2,7 @@ class TracesController < ApplicationController
   include UserMethods
   include PaginationMethods
 
-  layout "site", :except => :georss
+  layout "site"
 
   before_action :authorize_web
   before_action :set_locale
@@ -192,17 +192,6 @@ class TracesController < ApplicationController
     head :not_found
   end
 
-  def georss
-    @traces = Trace.visible_to_all.visible
-
-    @traces = @traces.joins(:user).where(:users => { :display_name => params[:display_name] }) if params[:display_name]
-
-    @traces = @traces.tagged(params[:tag]) if params[:tag]
-    @traces = @traces.order("timestamp DESC")
-    @traces = @traces.limit(20)
-    @traces = @traces.includes(:user)
-  end
-
   private
 
   def do_create(file, tags, description, visibility)
diff --git a/app/controllers/users/statuses_controller.rb b/app/controllers/users/statuses_controller.rb
new file mode 100644 (file)
index 0000000..a66782e
--- /dev/null
@@ -0,0 +1,36 @@
+module Users
+  class StatusesController < ApplicationController
+    layout "site"
+
+    before_action :authorize_web
+    before_action :set_locale
+    before_action :check_database_readable
+
+    authorize_resource :class => :user_status
+
+    before_action :lookup_user_by_name
+
+    ##
+    # sets a user's status
+    def update
+      @user.activate! if params[:event] == "activate"
+      @user.confirm! if params[:event] == "confirm"
+      @user.unconfirm! if params[:event] == "unconfirm"
+      @user.hide! if params[:event] == "hide"
+      @user.unhide! if params[:event] == "unhide"
+      @user.unsuspend! if params[:event] == "unsuspend"
+      @user.soft_destroy! if params[:event] == "soft_destroy" # destroy a user, marking them as deleted and removing personal data
+      redirect_to user_path(params[:user_display_name])
+    end
+
+    private
+
+    ##
+    # ensure that there is a "user" instance variable
+    def lookup_user_by_name
+      @user = User.find_by!(:display_name => params[:user_display_name])
+    rescue ActiveRecord::RecordNotFound
+      redirect_to user_path(params[:user_display_name]) unless @user
+    end
+  end
+end
index 904b960a28e157ec9c7484179debcd4efa3d637e..a0be87bdc11fa6417601b6e278030fc673825515 100644 (file)
@@ -14,7 +14,6 @@ class UsersController < ApplicationController
 
   before_action :check_database_writable, :only => [:new, :go_public]
   before_action :require_cookies, :only => [:new]
-  before_action :lookup_user_by_name, :only => [:set_status, :destroy]
 
   allow_thirdparty_images :only => :show
   allow_social_login :only => :new
@@ -98,13 +97,6 @@ class UsersController < ApplicationController
     end
   end
 
-  ##
-  # destroy a user, marking them as deleted and removing personal data
-  def destroy
-    @user.soft_destroy!
-    redirect_to user_path(:display_name => params[:display_name])
-  end
-
   def go_public
     current_user.data_public = true
     current_user.save
@@ -112,18 +104,6 @@ class UsersController < ApplicationController
     redirect_to edit_account_path
   end
 
-  ##
-  # sets a user's status
-  def set_status
-    @user.activate! if params[:event] == "activate"
-    @user.confirm! if params[:event] == "confirm"
-    @user.unconfirm! if params[:event] == "unconfirm"
-    @user.hide! if params[:event] == "hide"
-    @user.unhide! if params[:event] == "unhide"
-    @user.unsuspend! if params[:event] == "unsuspend"
-    redirect_to user_path(:display_name => params[:display_name])
-  end
-
   ##
   # omniauth success callback
   def auth_success
@@ -237,14 +217,6 @@ class UsersController < ApplicationController
     end
   end
 
-  ##
-  # ensure that there is a "user" instance variable
-  def lookup_user_by_name
-    @user = User.find_by(:display_name => params[:display_name])
-  rescue ActiveRecord::RecordNotFound
-    redirect_to :action => "view", :display_name => params[:display_name] unless @user
-  end
-
   ##
   # return permitted user parameters
   def user_params
index 4605658f65c0a3f1f0202cd70b7e3934adad9f96..d3bc5ed9ac085cf0259ebdf37a7a085bbe1caa30 100644 (file)
@@ -32,7 +32,7 @@ module ChangesetsHelper
 
   def changeset_index_title(params, user)
     if params[:friends] && user
-      t "changesets.index.title_friend"
+      t "changesets.index.title_followed"
     elsif params[:nearby] && user
       t "changesets.index.title_nearby"
     elsif params[:display_name]
index 1dd13fb2d220787af6562f11281abb6bd0da07cc..6098162852039a0078c927b7fce5b717e681196c 100644 (file)
@@ -119,16 +119,16 @@ class UserMailer < ApplicationMailer
     end
   end
 
-  def friendship_notification(friendship)
-    with_recipient_locale friendship.befriendee do
-      @friendship = friendship
-      @viewurl = user_url(@friendship.befriender)
-      @friendurl = make_friend_url(@friendship.befriender)
-      @author = @friendship.befriender.display_name
-
-      attach_user_avatar(@friendship.befriender)
-      mail :to => friendship.befriendee.email,
-           :subject => t(".subject", :user => friendship.befriender.display_name)
+  def follow_notification(follow)
+    with_recipient_locale follow.following do
+      @follow = follow
+      @viewurl = user_url(@follow.follower)
+      @followurl = follow_url(@follow.follower)
+      @author = @follow.follower.display_name
+
+      attach_user_avatar(@follow.follower)
+      mail :to => follow.following.email,
+           :subject => t(".subject", :user => follow.follower.display_name)
     end
   end
 
similarity index 69%
rename from app/models/friendship.rb
rename to app/models/follow.rb
index 2b1c7ce00a845d0e85181e50b5c495e718237250..ebf3ad735da1c5dd2f3fa050506060aa1cdc888c 100644 (file)
@@ -18,9 +18,9 @@
 #  friends_user_id_fkey         (user_id => users.id)
 #
 
-class Friendship < ApplicationRecord
+class Follow < ApplicationRecord
   self.table_name = "friends"
 
-  belongs_to :befriender, :class_name => "User", :foreign_key => :user_id
-  belongs_to :befriendee, :class_name => "User", :foreign_key => :friend_user_id
+  belongs_to :follower, :class_name => "User", :foreign_key => :user_id, :inverse_of => :follows
+  belongs_to :following, :class_name => "User", :foreign_key => :friend_user_id, :inverse_of => :follows
 end
index 917faca2184f38f79999d7ea5e31f5768727d0d7..16f733c882d30f002f3e7765c703d9bf7bf5254f 100644 (file)
@@ -58,8 +58,8 @@ class User < ApplicationRecord
   has_many :new_messages, -> { where(:to_user_visible => true, :muted => false, :message_read => false).order(:sent_on => :desc) }, :class_name => "Message", :foreign_key => :to_user_id
   has_many :sent_messages, -> { where(:from_user_visible => true).order(:sent_on => :desc).preload(:sender, :recipient) }, :class_name => "Message", :foreign_key => :from_user_id
   has_many :muted_messages, -> { where(:to_user_visible => true, :muted => true).order(:sent_on => :desc).preload(:sender, :recipient) }, :class_name => "Message", :foreign_key => :to_user_id
-  has_many :friendships, -> { joins(:befriendee).where(:users => { :status => %w[active confirmed] }) }
-  has_many :friends, :through => :friendships, :source => :befriendee
+  has_many :follows, -> { joins(:following).where(:users => { :status => %w[active confirmed] }) }
+  has_many :followings, :through => :follows, :source => :following
   has_many :preferences, :class_name => "UserPreference"
   has_many :changesets, -> { order(:created_at => :desc) }, :inverse_of => :user
   has_many :changeset_comments, :foreign_key => :author_id, :inverse_of => :author
@@ -282,8 +282,8 @@ class User < ApplicationRecord
     OSM::GreatCircle.new(home_lat, home_lon).distance(nearby_user.home_lat, nearby_user.home_lon)
   end
 
-  def friends_with?(new_friend)
-    friendships.exists?(:befriendee => new_friend)
+  def follows?(user)
+    follows.exists?(:following => user)
   end
 
   ##
@@ -411,12 +411,12 @@ class User < ApplicationRecord
     max_messages.clamp(0, Settings.max_messages_per_hour)
   end
 
-  def max_friends_per_hour
+  def max_follows_per_hour
     account_age_in_seconds = Time.now.utc - created_at
     account_age_in_hours = account_age_in_seconds / 3600
-    recent_friends = Friendship.where(:befriendee => self).where(:created_at => Time.now.utc - 3600..).count
-    max_friends = account_age_in_hours.ceil + recent_friends - (active_reports * 10)
-    max_friends.clamp(0, Settings.max_friends_per_hour)
+    recent_follows = Follow.where(:following => self).where(:created_at => Time.now.utc - 3600..).count
+    max_follows = account_age_in_hours.ceil + recent_follows - (active_reports * 10)
+    max_follows.clamp(0, Settings.max_follows_per_hour)
   end
 
   def max_changeset_comments_per_hour
index 16f109c9f99b77d23ced80ae371be78bae461c4d..7a10b12e3d281c181248d9bd5b11312eea62c7b0 100644 (file)
     <small class="form-text text-body-secondary">(<a href="<%= t ".openid.link" %>" target="_new"><%= t ".openid.link text" %></a>)</small>
   </fieldset>
 
-  <div class="mb-3">
-    <label class="form-label"><%= t ".public editing.heading" %></label>
-    <span class="form-text text-body-secondary">
-      <% if current_user.data_public? %>
-        <%= t ".public editing.enabled" %>
-        (<a href="<%= t ".public editing.enabled link" %>" target="_new"><%= t ".public editing.enabled link text" %></a>)
-      <% else %>
-        <%= t ".public editing.disabled" %>
-        (<a href="#public"><%= t ".public editing.disabled link text" %></a>)
-      <% end %>
-    </span>
-  </div>
-
   <div class="mb-3">
     <label class="form-label"><%= t ".contributor terms.heading" %></label>
     <span class="form-text text-body-secondary">
index a04295d31304fa98f204c84d33a23a465284e1a1..840ad19d029d230c69802c1d04e188218b961d85 100644 (file)
@@ -6,10 +6,11 @@ json.to_display_name message.recipient.display_name
 json.title message.title
 json.sent_on message.sent_on.xmlschema
 
+json.message_read message.message_read if current_user.id == message.to_user_id
+
 if current_user.id == message.from_user_id
   json.deleted !message.from_user_visible
 elsif current_user.id == message.to_user_id
-  json.message_read message.message_read
   json.deleted !message.to_user_visible
 end
 
index 64ac9e3bcc0d8aecb442a4661221496f7d65cc12..2ab307f1ab1e848cd0b433832d6c5f479bb6e401 100644 (file)
@@ -8,10 +8,11 @@ attrs = {
   "body_format" => message.body_format
 }
 
+attrs["message_read"] = message.message_read if current_user.id == message.to_user_id
+
 if current_user.id == message.from_user_id
   attrs["deleted"] = !message.from_user_visible
 elsif current_user.id == message.to_user_id
-  attrs["message_read"] = message.message_read
   attrs["deleted"] = !message.to_user_visible
 end
 
index 759faab8f7d50d713a301ffab32b29d6ba959e65..8e78524decb463730cfc2f863151aba6639b8fa1 100644 (file)
@@ -1,7 +1,7 @@
 <% user_data = {
      :lon => contact.home_lon,
      :lat => contact.home_lat,
-     :icon => image_path(type == "friend" ? "marker-blue.png" : "marker-green.png"),
+     :icon => image_path(type == "following" ? "marker-blue.png" : "marker-green.png"),
      :description => render(:partial => "popup", :object => contact, :locals => { :type => type })
    } %>
 <%= tag.div :class => "clearfix row", :data => { :user => user_data } do %>
       <ul class='clearfix text-body-secondary'>
         <li><%= link_to t("users.show.send message"), new_message_path(contact) %></li>
         <li>
-          <% if current_user.friends_with?(contact) %>
-            <%= link_to t("users.show.remove as friend"), remove_friend_path(:display_name => contact.display_name, :referer => request.fullpath), :method => :post %>
+          <% if current_user.follows?(contact) %>
+            <%= link_to t("users.show.unfollow"), follow_path(:display_name => contact.display_name, :referer => request.fullpath), :method => :delete %>
           <% else %>
-            <%= link_to t("users.show.add as friend"), make_friend_path(:display_name => contact.display_name, :referer => request.fullpath), :method => :post %>
+            <%= link_to t("users.show.follow"), follow_path(:display_name => contact.display_name, :referer => request.fullpath), :method => :post %>
           <% end %>
         </li>
       </ul>
index c4b595e9b931dc6ba5e79be030105f082433a3d3..70dae774e86eef0a3e5e9567b2f75d46d9791755 100644 (file)
         <%= tag.div "", :id => "map", :class => "content_map border border-secondary-subtle rounded z-0", :data => { :user => user_data } %>
       <% end %>
 
-      <% friends = @user.friends %>
-      <% nearby = @user.nearby - friends %>
+      <% followings = @user.followings %>
+      <% nearby = @user.nearby - followings %>
     </div>
 
     <div class="col-md">
-      <h2><%= t ".my friends" %></h2>
+      <h2><%= t ".followings" %></h2>
 
-      <% if friends.empty? %>
-        <%= t ".no friends" %>
+      <% if followings.empty? %>
+        <%= t ".no followings" %>
       <% else %>
         <nav class='secondary-actions mb-3'>
           <ul class='clearfix'>
-            <li><%= link_to t(".friends_changesets"), friend_changesets_path %></li>
-            <li><%= link_to t(".friends_diaries"), friends_diary_entries_path %></li>
+            <li><%= link_to t(".followed_changesets"), friend_changesets_path %></li>
+            <li><%= link_to t(".followed_diaries"), friends_diary_entries_path %></li>
           </ul>
         </nav>
         <div>
-          <%= render :partial => "contact", :collection => friends, :locals => { :type => "friend" } %>
+          <%= render :partial => "contact", :collection => followings, :locals => { :type => "following" } %>
         </div>
       <% end %>
 
diff --git a/app/views/follows/show.html.erb b/app/views/follows/show.html.erb
new file mode 100644 (file)
index 0000000..1877362
--- /dev/null
@@ -0,0 +1,10 @@
+<% content_for :heading do %>
+  <h1><%= t(@already_follows ? ".unfollow.heading" : ".follow.heading", :user => @user.display_name) %></h1>
+<% end %>
+
+<%= bootstrap_form_tag :method => (@already_follows ? :delete : :post) do |f| %>
+  <% if params[:referer] -%>
+  <%= f.hidden_field :referer, :value => params[:referer] %>
+  <% end -%>
+  <%= f.primary @already_follows ? t(".unfollow.button") : t(".follow.button") %>
+<% end %>
diff --git a/app/views/friendships/make_friend.html.erb b/app/views/friendships/make_friend.html.erb
deleted file mode 100644 (file)
index f5c2b9c..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-<% content_for :heading do %>
-  <h1><%= t ".heading", :user => @friend.display_name %></h1>
-<% end %>
-
-<%= bootstrap_form_tag do |f| %>
-  <% if params[:referer] -%>
-  <%= f.hidden_field :referer, :value => params[:referer] %>
-  <% end -%>
-  <%= f.primary t(".button") %>
-<% end %>
diff --git a/app/views/friendships/remove_friend.html.erb b/app/views/friendships/remove_friend.html.erb
deleted file mode 100644 (file)
index f5c2b9c..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-<% content_for :heading do %>
-  <h1><%= t ".heading", :user => @friend.display_name %></h1>
-<% end %>
-
-<%= bootstrap_form_tag do |f| %>
-  <% if params[:referer] -%>
-  <%= f.hidden_field :referer, :value => params[:referer] %>
-  <% end -%>
-  <%= f.primary t(".button") %>
-<% end %>
similarity index 88%
rename from app/views/traces/georss.rss.builder
rename to app/views/traces/feeds/show.rss.builder
index ad5bd45b2248d663bb94a514d4dc82dd1bf1b2b1..dc10a12d6ca801b96f109315bdde137dce673c73 100644 (file)
@@ -7,14 +7,14 @@ xml.rss("version" => "2.0",
   xml.channel do
     xml.title t(".title")
     xml.description t(".title")
-    xml.link url_for(:controller => :traces, :action => :index, :only_path => false)
+    xml.link url_for(:controller => "/traces", :action => :index, :only_path => false)
 
     xml.image do
       xml.url image_url("mag_map-rss2.0.png")
       xml.title t(".title")
       xml.width 100
       xml.height 100
-      xml.link url_for(:controller => :traces, :action => :index, :only_path => false)
+      xml.link url_for(:controller => "/traces", :action => :index, :only_path => false)
     end
 
     @traces.each do |trace|
index 4311ba082e39de12dd945f3a66bf011c9edfabc3..dfa5f03de7d219b5ec7661b38502ab2826fac610 100644 (file)
@@ -16,8 +16,8 @@
 
   <div class="d-flex flex-column flex-md-row-reverse align-items-md-end">
     <div class="pb-1 ps-1 d-flex flex-wrap flex-shrink-0 gap-1 justify-content-end">
-      <%= link_to({ :action => :georss, :display_name => @target_user&.display_name, :tag => params[:tag] },
-                  { :class => "btn btn-secondary btn-sm" }) do %>
+      <%= link_to traces_feed_path(:display_name => @target_user&.display_name, :tag => params[:tag]),
+                  :class => "btn btn-secondary btn-sm" do %>
         <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" class="align-text-bottom">
           <circle cx="2" cy="14" r="2" fill="white" />
           <path d="M 8 14 a 6 6 0 0 0 -6 -6 M 14 14 a 12 12 0 0 0 -12 -12" fill="none" stroke="white" stroke-width="3" stroke-linecap="round" />
@@ -66,7 +66,7 @@
 <% end %>
 
 <% content_for :auto_discovery_link_tag do %>
-  <%= auto_discovery_link_tag :rss, :action => "georss", :display_name => @target_user&.display_name, :tag => params[:tag] %>
+  <%= auto_discovery_link_tag :rss, traces_feed_path(:display_name => @target_user&.display_name, :tag => params[:tag]) %>
 <% end %>
 
 <% if @traces.size > 0 %>
diff --git a/app/views/user_mailer/follow_notification.html.erb b/app/views/user_mailer/follow_notification.html.erb
new file mode 100644 (file)
index 0000000..48b037d
--- /dev/null
@@ -0,0 +1,11 @@
+<p><%= t ".hi", :to_user => @follow.following.display_name %></p>
+
+<p><%= t ".followed_you", :user => @follow.follower.display_name %></p>
+
+<%= message_body do %>
+  <p><%= t ".see_their_profile_html", :userurl => link_to(@viewurl, @viewurl) %></p>
+
+  <% unless @follow.following.follows?(@follow.follower) -%>
+  <p><%= t ".follow_them_html", :followurl => link_to(@followurl, @followurl) %></p>
+  <% end -%>
+<% end %>
diff --git a/app/views/user_mailer/follow_notification.text.erb b/app/views/user_mailer/follow_notification.text.erb
new file mode 100644 (file)
index 0000000..7f9ba20
--- /dev/null
@@ -0,0 +1,9 @@
+<%= t ".hi", :to_user => @follow.following.display_name %>
+
+<%= t '.followed_you', :user => @follow.follower.display_name %>
+
+<%= t '.see_their_profile', :userurl => @viewurl %>
+
+<% unless @follow.following.follows?(@follow.follower) -%>
+<%= t '.follow_them', :followurl => @followurl %>
+<% end -%>
diff --git a/app/views/user_mailer/friendship_notification.html.erb b/app/views/user_mailer/friendship_notification.html.erb
deleted file mode 100644 (file)
index 40af762..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-<p><%= t ".hi", :to_user => @friendship.befriendee.display_name %></p>
-
-<p><%= t ".had_added_you", :user => @friendship.befriender.display_name %></p>
-
-<%= message_body do %>
-  <p><%= t ".see_their_profile_html", :userurl => link_to(@viewurl, @viewurl) %></p>
-
-  <% unless @friendship.befriendee.friends_with?(@friendship.befriender) -%>
-  <p><%= t ".befriend_them_html", :befriendurl => link_to(@friendurl, @friendurl) %></p>
-  <% end -%>
-<% end %>
diff --git a/app/views/user_mailer/friendship_notification.text.erb b/app/views/user_mailer/friendship_notification.text.erb
deleted file mode 100644 (file)
index 104b0f1..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<%= t ".hi", :to_user => @friendship.befriendee.display_name %>
-
-<%= t '.had_added_you', :user => @friendship.befriender.display_name %>
-
-<%= t '.see_their_profile', :userurl => @viewurl %>
-
-<% unless @friendship.befriendee.friends_with?(@friendship.befriender) -%>
-<%= t '.befriend_them', :befriendurl => @friendurl %>
-<% end -%>
index c168972aac44a945a8118454ec5c97e807505bdc..b2247c0d01455d59cf68b9843a4bad4d6e9cdcbf 100644 (file)
             </li>
             <% if current_user %>
               <li>
-                <% if current_user.friends_with?(@user) %>
-                  <%= link_to t(".remove as friend"), remove_friend_path(:display_name => @user.display_name), :method => :post %>
+                <% if current_user.follows?(@user) %>
+                  <%= link_to t(".unfollow"), follow_path(:display_name => @user.display_name), :method => :delete %>
                 <% else %>
-                  <%= link_to t(".add as friend"), make_friend_path(:display_name => @user.display_name), :method => :post %>
+                  <%= link_to t(".follow"), follow_path(:display_name => @user.display_name), :method => :post %>
                 <% end %>
               </li>
             <% end %>
         </small>
       </div>
 
-      <% if can?(:set_status, User) || can?(:destroy, User) %>
+      <% if can?(:update, :user_status) %>
         <nav class='secondary-actions'>
           <ul class='clearfix'>
-            <% if can? :set_status, User %>
-              <% if @user.may_activate? %>
-                <li>
-                  <%= link_to t(".activate_user"), set_status_user_path(:event => "activate", :display_name => @user.display_name), :method => :post, :data => { :confirm => t(".confirm") } %>
-                </li>
-              <% end %>
-
-              <% if @user.may_unsuspend? %>
-                <li>
-                  <%= link_to t(".unsuspend_user"), set_status_user_path(:event => "unsuspend", :display_name => @user.display_name), :method => :post, :data => { :confirm => t(".confirm") } %>
-                </li>
-              <% end %>
-
-              <% if @user.may_confirm? %>
-                <li>
-                  <%= link_to t(".confirm_user"), set_status_user_path(:event => "confirm", :display_name => @user.display_name), :method => :post, :data => { :confirm => t(".confirm") } %>
-                </li>
-              <% end %>
-
-              <% if @user.may_unconfirm? %>
-                <li>
-                  <%= link_to t(".unconfirm_user"), set_status_user_path(:event => "unconfirm", :display_name => @user.display_name), :method => :post, :data => { :confirm => t(".confirm") } %>
-                </li>
-              <% end %>
-
-              <% if @user.may_hide? %>
-                <li>
-                  <%= link_to t(".hide_user"), set_status_user_path(:event => "hide", :display_name => @user.display_name), :method => :post, :data => { :confirm => t(".confirm") } %>
-                </li>
-              <% end %>
-
-              <% if @user.may_unhide? %>
-                <li>
-                  <%= link_to t(".unhide_user"), set_status_user_path(:event => "unhide", :display_name => @user.display_name), :method => :post, :data => { :confirm => t(".confirm") } %>
-                </li>
-              <% end %>
-            <% end %>
-
-            <% if can?(:destroy, User) && @user.may_soft_destroy? %>
-              <li>
-                <%= link_to t(".delete_user"), user_path(:display_name => @user.display_name), :method => :delete, :data => { :confirm => t(".confirm") } %>
+            <% if @user.may_activate? %>
+              <li>
+                <%= link_to t(".activate_user"), user_status_path(@user, :event => "activate"), :method => :put, :data => { :confirm => t(".confirm") } %>
+              </li>
+            <% end %>
+
+            <% if @user.may_unsuspend? %>
+              <li>
+                <%= link_to t(".unsuspend_user"), user_status_path(@user, :event => "unsuspend"), :method => :put, :data => { :confirm => t(".confirm") } %>
+              </li>
+            <% end %>
+
+            <% if @user.may_confirm? %>
+              <li>
+                <%= link_to t(".confirm_user"), user_status_path(@user, :event => "confirm"), :method => :put, :data => { :confirm => t(".confirm") } %>
+              </li>
+            <% end %>
+
+            <% if @user.may_unconfirm? %>
+              <li>
+                <%= link_to t(".unconfirm_user"), user_status_path(@user, :event => "unconfirm"), :method => :put, :data => { :confirm => t(".confirm") } %>
+              </li>
+            <% end %>
+
+            <% if @user.may_hide? %>
+              <li>
+                <%= link_to t(".hide_user"), user_status_path(@user, :event => "hide"), :method => :put, :data => { :confirm => t(".confirm") } %>
+              </li>
+            <% end %>
+
+            <% if @user.may_unhide? %>
+              <li>
+                <%= link_to t(".unhide_user"), user_status_path(@user, :event => "unhide"), :method => :put, :data => { :confirm => t(".confirm") } %>
+              </li>
+            <% end %>
+
+            <% if @user.may_soft_destroy? %>
+              <li>
+                <%= link_to t(".delete_user"), user_status_path(@user, :event => "soft_destroy"), :method => :put, :data => { :confirm => t(".confirm") } %>
               </li>
             <% end %>
           </ul>
index 98ef2e691557d3264a9eed129345ad092d310c18..30cf29d2988cd4b41a0648cea783736e88cccc2e 100644 (file)
@@ -39,6 +39,7 @@
         href: "https://www.thunderforest.com/"
 
 - leafletOsmId: "TransportMap"
+  leafletOsmDarkId: "TransportDarkMap"
   code: "T"
   layerId: "transportmap"
   nameId: "transport_map"
index 21a7ce0f2aa85d52c87f0d7f727cb1abcb6485dd..ba70cbfb6580cb9042b6a732fbda4ecbb58a1f0b 100644 (file)
@@ -89,7 +89,7 @@ en:
         support_url: Support URL
         allow_read_prefs:  read their user preferences
         allow_write_prefs: modify their user preferences
-        allow_write_diary: create diary entries, comments and make friends
+        allow_write_diary: create diary entries and comments
         allow_write_api:   modify the map
         allow_read_gpx:    read their private GPS traces
         allow_write_gpx:   upload GPS traces
@@ -251,13 +251,6 @@ en:
       openid:
         link: "https://wiki.openstreetmap.org/wiki/OpenID"
         link text: "what is this?"
-      public editing:
-        heading: "Public editing"
-        enabled: "Enabled. Not anonymous and can edit data."
-        enabled link: "https://wiki.openstreetmap.org/wiki/Anonymous_edits"
-        enabled link text: "what is this?"
-        disabled: "Disabled and cannot edit data, all previous edits are anonymous."
-        disabled link text: "why can't I edit?"
       contributor terms:
         heading: "Contributor Terms"
         agreed: "You have agreed to the new Contributor Terms."
@@ -476,7 +469,7 @@ en:
       title: "Changesets"
       title_user: "Changesets by %{user}"
       title_user_link_html: "Changesets by %{user_link}"
-      title_friend: "Changesets by my friends"
+      title_followed: "Changesets by followings"
       title_nearby: "Changesets by nearby users"
       empty: "No changesets found."
       empty_area: "No changesets in this area."
@@ -542,17 +535,17 @@ en:
     popup:
       your location: "Your location"
       nearby mapper: "Nearby mapper"
-      friend: "Friend"
+      following: "Following"
     show:
       title: My Dashboard
       no_home_location_html: "%{edit_profile_link} and set your home location to see nearby users."
       edit_your_profile: Edit your profile
-      my friends: My friends
-      no friends: You have not added any friends yet.
+      followings: Followings
+      no followings: You have not followed any user yet.
       nearby users: "Other nearby users"
       no nearby users: "There are no other users who admit to mapping nearby yet."
-      friends_changesets: "friends' changesets"
-      friends_diaries: "friends' diary entries"
+      followed_changesets: "changesets"
+      followed_diaries: "diary entries"
       nearby_changesets: "nearby user changesets"
       nearby_diaries: "nearby user diary entries"
   diary_entries:
@@ -563,7 +556,7 @@ en:
       use_map_link: Use Map
     index:
       title: "Users' Diaries"
-      title_friends: "Friends' Diaries"
+      title_followed: "Followings' Diaries"
       title_nearby: "Nearby Users' Diaries"
       user_title: "%{user}'s Diary"
       in_language_title: "Diary Entries in %{language}"
@@ -684,19 +677,22 @@ en:
     not_found:
       title: File not found
       description: Couldn't find a file/directory/API operation by that name on the OpenStreetMap server (HTTP 404)
-  friendships:
-    make_friend:
-      heading: "Add %{user} as a friend?"
-      button: "Add as friend"
-      success: "%{name} is now your friend!"
-      failed: "Sorry, failed to add %{name} as a friend."
-      already_a_friend: "You are already friends with %{name}."
-      limit_exceeded: "You have friended a lot of users recently. Please wait a while before trying to friend any more."
-    remove_friend:
-      heading: "Unfriend %{user}?"
-      button: "Unfriend"
-      success: "%{name} was removed from your friends."
-      not_a_friend: "%{name} is not one of your friends."
+  follows:
+    show:
+      follow:
+        heading: "Do you want to follow %{user}?"
+        button: "Follow User"
+      unfollow:
+        heading: "Do you want to unfollow %{user}?"
+        button: "Unfollow User"
+    create:
+      success: "You are now following %{name}!"
+      failed: "Sorry, your request to follow %{name} has failed."
+      already_followed: "You already follow %{name}."
+      limit_exceeded: "You have followed a lot of users recently. Please wait a while before trying to follow any more."
+    destroy:
+      success: "You successfully unfollowed %{name}."
+      not_followed: "You are not following %{name}."
   geocoder:
     search:
       title:
@@ -1658,14 +1654,14 @@ en:
       header_html: "%{from_user} has sent you a message through OpenStreetMap with the subject %{subject}:"
       footer: "You can also read the message at %{readurl} and you can send a message to the author at %{replyurl}"
       footer_html: "You can also read the message at %{readurl} and you can send a message to the author at %{replyurl}"
-    friendship_notification:
+    follow_notification:
       hi: "Hi %{to_user},"
-      subject: "[OpenStreetMap] %{user} added you as a friend"
-      had_added_you: "%{user} has added you as a friend on OpenStreetMap."
+      subject: "[OpenStreetMap] %{user} followed you"
+      followed_you: "%{user} is now following you on OpenStreetMap."
       see_their_profile: "You can see their profile at %{userurl}."
       see_their_profile_html: "You can see their profile at %{userurl}."
-      befriend_them: "You can also add them as a friend at %{befriendurl}."
-      befriend_them_html: "You can also add them as a friend at %{befriendurl}."
+      follow_them: "You can also follow them at %{followurl}."
+      follow_them_html: "You can also follow them at %{followurl}."
     gpx_details:
       details: "Your file details:"
       filename: Filename
@@ -2620,13 +2616,14 @@ en:
     offline:
       heading: "GPX Storage Offline"
       message: "The GPX file storage and upload system is currently unavailable."
-    georss:
-      title: "OpenStreetMap GPS Traces"
-    description:
-      description_with_count:
-        one: "GPX file with %{count} point from %{user}"
-        other: "GPX file with %{count} points from %{user}"
-      description_without_count: "GPX file from %{user}"
+    feeds:
+      show:
+        title: "OpenStreetMap GPS Traces"
+      description:
+        description_with_count:
+          one: "GPX file with %{count} point from %{user}"
+          other: "GPX file with %{count} points from %{user}"
+        description_without_count: "GPX file from %{user}"
   application:
     permission_denied: You do not have permission to access that action
     require_cookies:
@@ -2690,7 +2687,7 @@ en:
       openid: Sign-in using OpenStreetMap
       read_prefs: Read user preferences
       write_prefs: Modify user preferences
-      write_diary: Create diary entries, comments and make friends
+      write_diary: Create diary entries and comments
       write_api: Modify the map
       read_gpx: Read private GPS traces
       write_gpx: Upload GPS traces
@@ -2809,8 +2806,8 @@ en:
       edits: Edits
       traces: Traces
       notes: Map Notes
-      remove as friend: Unfriend
-      add as friend: Add Friend
+      unfollow: Unfollow
+      follow: Follow
       mapper since: "Mapper since:"
       last map edit: "Last map edit:"
       no activity yet: "No activity yet"
index 9cb3a63edc1878dd2d126a3d0c5c2653549eb167..6efd17759e273e8a0d27d3e79634214d7cc34eb9 100644 (file)
@@ -64,11 +64,11 @@ OpenStreetMap::Application.routes.draw do
     get "relations" => "relations#index"
 
     get "map" => "map#index"
-
-    get "trackpoints" => "tracepoints#index"
   end
 
   namespace :api, :path => "api/0.6" do
+    resources :tracepoints, :path => "trackpoints", :only => :index
+
     resources :users, :only => :index
     resources :users, :path => "user", :id => /\d+/, :only => :show
     resources :user_traces, :path => "user/gpx_files", :module => :users, :controller => :traces, :only => :index
@@ -211,30 +211,30 @@ OpenStreetMap::Application.routes.draw do
   post "/preview/:type" => "site#preview", :as => :preview
 
   # traces
-  resources :traces, :except => [:show]
+  resources :traces, :id => /\d+/, :except => [:show]
   get "/user/:display_name/traces/tag/:tag/page/:page", :page => /[1-9][0-9]*/, :to => redirect(:path => "/user/%{display_name}/traces/tag/%{tag}")
   get "/user/:display_name/traces/tag/:tag" => "traces#index"
   get "/user/:display_name/traces/page/:page", :page => /[1-9][0-9]*/, :to => redirect(:path => "/user/%{display_name}/traces")
   get "/user/:display_name/traces" => "traces#index"
-  get "/user/:display_name/traces/tag/:tag/rss" => "traces#georss", :defaults => { :format => :rss }
-  get "/user/:display_name/traces/rss" => "traces#georss", :defaults => { :format => :rss }
-  get "/user/:display_name/traces/:id" => "traces#show", :as => "show_trace"
-  scope "/user/:display_name/traces/:trace_id", :module => :traces do
+  get "/user/:display_name/traces/:id" => "traces#show", :id => /\d+/, :as => "show_trace"
+  scope "/user/:display_name/traces/:trace_id", :module => :traces, :trace_id => /\d+/ do
     get "picture" => "pictures#show", :as => "trace_picture"
     get "icon" => "icons#show", :as => "trace_icon"
   end
   get "/traces/tag/:tag/page/:page", :page => /[1-9][0-9]*/, :to => redirect(:path => "/traces/tag/%{tag}")
   get "/traces/tag/:tag" => "traces#index"
   get "/traces/page/:page", :page => /[1-9][0-9]*/, :to => redirect(:path => "/traces")
-  get "/traces/tag/:tag/rss" => "traces#georss", :defaults => { :format => :rss }
-  get "/traces/rss" => "traces#georss", :defaults => { :format => :rss }
   get "/traces/mine/tag/:tag/page/:page", :page => /[1-9][0-9]*/, :to => redirect(:path => "/traces/mine/tag/%{tag}")
   get "/traces/mine/tag/:tag" => "traces#mine"
   get "/traces/mine/page/:page", :page => /[1-9][0-9]*/, :to => redirect(:path => "/traces/mine")
   get "/traces/mine" => "traces#mine"
   get "/trace/create", :to => redirect(:path => "/traces/new")
   get "/trace/:id/data" => "traces#data", :id => /\d+/, :as => "trace_data"
-  get "/trace/:id/edit", :to => redirect(:path => "/traces/%{id}/edit")
+  get "/trace/:id/edit", :id => /\d+/, :to => redirect(:path => "/traces/%{id}/edit")
+
+  namespace :traces, :path => "" do
+    resource :feed, :path => "(/user/:display_name)/traces(/tag/:tag)/rss", :only => :show, :defaults => { :format => :rss }
+  end
 
   # diary pages
   resources :diary_entries, :path => "diary", :only => [:new, :create, :index] do
@@ -266,15 +266,15 @@ OpenStreetMap::Application.routes.draw do
 
   # user pages
   get "/user/terms", :to => redirect(:path => "/account/terms")
-  resources :users, :path => "user", :param => :display_name, :only => [:new, :create, :show, :destroy] do
+  resources :users, :path => "user", :param => :display_name, :only => [:new, :create, :show] do
     resource :role, :controller => "user_roles", :path => "roles/:role", :only => [:create, :destroy]
     scope :module => :users do
       resource :issued_blocks, :path => "blocks_by", :only => :show
       resource :received_blocks, :path => "blocks", :only => [:show, :edit, :destroy]
+      resource :status, :only => :update
     end
   end
   get "/user/:display_name/account", :to => redirect(:path => "/account/edit")
-  post "/user/:display_name/set_status" => "users#set_status", :as => :set_status_user
 
   resource :account, :only => [:edit, :update, :destroy] do
     scope :module => :accounts do
@@ -289,8 +289,12 @@ OpenStreetMap::Application.routes.draw do
   resource :profile, :only => [:edit, :update]
 
   # friendships
-  match "/user/:display_name/make_friend" => "friendships#make_friend", :via => [:get, :post], :as => "make_friend"
-  match "/user/:display_name/remove_friend" => "friendships#remove_friend", :via => [:get, :post], :as => "remove_friend"
+  scope "/user/:display_name" do
+    resource :follow, :only => [:create, :destroy, :show], :path => "follow"
+
+    get "make_friend", :to => redirect("/user/%{display_name}/follow")
+    get "remove_friend", :to => redirect("/user/%{display_name}/follow")
+  end
 
   # user lists
   namespace :users do
index db871775e784d4cfe5ceb29a71bfbdee7c89b2ed..c79199145163359dd3a7efbc6dcb6e539c4037e1 100644 (file)
@@ -63,8 +63,8 @@ max_messages_per_hour: 60
 default_message_query_limit: 100
 # Maximum number of messages returned by inbox and outbox message api
 max_message_query_limit: 100
-# Rate limit for friending
-max_friends_per_hour: 60
+# Rate limit for following
+max_follows_per_hour: 60
 # Rate limit for changeset comments
 min_changeset_comments_per_hour: 1
 initial_changeset_comments_per_hour: 6
diff --git a/db/migrate/20250105154621_validate_foreign_key_on_notes.rb b/db/migrate/20250105154621_validate_foreign_key_on_notes.rb
new file mode 100644 (file)
index 0000000..8ff7df9
--- /dev/null
@@ -0,0 +1,5 @@
+class ValidateForeignKeyOnNotes < ActiveRecord::Migration[7.2]
+  def change
+    validate_foreign_key :notes, :users
+  end
+end
index fe0b4e481ae00779b100be603fb7c3f0aff59085..04b0dc17f13128dc12d1d7982a141243a1fe16e2 100644 (file)
@@ -3218,7 +3218,7 @@ ALTER TABLE ONLY public.note_comments
 --
 
 ALTER TABLE ONLY public.notes
-    ADD CONSTRAINT notes_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) NOT VALID;
+    ADD CONSTRAINT notes_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id);
 
 
 --
@@ -3408,6 +3408,7 @@ INSERT INTO "schema_migrations" (version) VALUES
 ('23'),
 ('22'),
 ('21'),
+('20250105154621'),
 ('20250104140952'),
 ('20241023004427'),
 ('20241022141247'),
index 806a4a505ee75e9de39a840141f3d6239169cf9b..0857d0e100fa6c1375d28bb6d28eec51156eef51 100644 (file)
@@ -222,6 +222,34 @@ module Api
       assert_equal msg.body, jsm["body"]
     end
 
+    def test_show_message_to_self_read
+      user = create(:user)
+      message = create(:message, :sender => user, :recipient => user)
+      auth_header = bearer_authorization_header user
+
+      get api_message_path(message), :headers => auth_header
+      assert_response :success
+      assert_equal "application/xml", response.media_type
+      assert_dom "message", :count => 1 do
+        assert_dom "> @message_read", "false"
+      end
+    end
+
+    def test_show_message_to_self_read_json
+      user = create(:user)
+      message = create(:message, :sender => user, :recipient => user)
+      auth_header = bearer_authorization_header user
+
+      get api_message_path(message, :format => "json"), :headers => auth_header
+      assert_response :success
+      assert_equal "application/json", response.media_type
+      js = ActiveSupport::JSON.decode(@response.body)
+      jsm = js["message"]
+      assert_not_nil jsm
+      assert jsm.key?("message_read")
+      assert_not jsm["message_read"]
+    end
+
     def test_update_status
       recipient = create(:user)
       sender = create(:user)
index aeea3f4f45f2ecfd64bc0ec4d2d80b0c9d8bb9d5..83c03af6c831fa65a8ef6441e0994aa8f416d522 100644 (file)
@@ -34,7 +34,7 @@ module Api
       maxlon = point.longitude + 0.001
       maxlat = point.latitude + 0.001
       bbox = "#{minlon},#{minlat},#{maxlon},#{maxlat}"
-      get trackpoints_path(:bbox => bbox)
+      get api_tracepoints_path(:bbox => bbox)
       assert_response :success
       assert_select "gpx[version='1.0'][creator='OpenStreetMap.org']", :count => 1 do
         assert_select "trk" do
@@ -56,7 +56,7 @@ module Api
       maxlon = point.longitude + 0.002
       maxlat = point.latitude + 0.002
       bbox = "#{minlon},#{minlat},#{maxlon},#{maxlat}"
-      get trackpoints_path(:bbox => bbox)
+      get api_tracepoints_path(:bbox => bbox)
       assert_response :success
       assert_select "gpx[version='1.0'][creator='OpenStreetMap.org']", :count => 1 do
         assert_select "trk", :count => 1 do
@@ -83,7 +83,7 @@ module Api
       maxlon = point.longitude + 0.002
       maxlat = point.latitude + 0.002
       bbox = "#{minlon},#{minlat},#{maxlon},#{maxlat}"
-      get trackpoints_path(:bbox => bbox)
+      get api_tracepoints_path(:bbox => bbox)
       assert_response :success
       assert_select "gpx[version='1.0'][creator='OpenStreetMap.org']", :count => 1 do
         assert_select "trk", :count => 1 do
@@ -100,26 +100,26 @@ module Api
     end
 
     def test_index_without_bbox
-      get trackpoints_path
+      get api_tracepoints_path
       assert_response :bad_request
       assert_equal "The parameter bbox is required", @response.body, "A bbox param was expected"
     end
 
     def test_traces_page_less_than_zero
       -10.upto(-1) do |i|
-        get trackpoints_path(:page => i, :bbox => "-0.1,-0.1,0.1,0.1")
+        get api_tracepoints_path(:page => i, :bbox => "-0.1,-0.1,0.1,0.1")
         assert_response :bad_request
         assert_equal "Page number must be greater than or equal to 0", @response.body, "The page number was #{i}"
       end
       0.upto(10) do |i|
-        get trackpoints_path(:page => i, :bbox => "-0.1,-0.1,0.1,0.1")
+        get api_tracepoints_path(:page => i, :bbox => "-0.1,-0.1,0.1,0.1")
         assert_response :success, "The page number was #{i} and should have been accepted"
       end
     end
 
     def test_bbox_too_big
       @badbigbbox.each do |bbox|
-        get trackpoints_path(:bbox => bbox)
+        get api_tracepoints_path(:bbox => bbox)
         assert_response :bad_request, "The bbox:#{bbox} was expected to be too big"
         assert_equal "The maximum bbox size is #{Settings.max_request_area}, and your request was too large. Either request a smaller area, or use planet.osm", @response.body, "bbox: #{bbox}"
       end
@@ -127,7 +127,7 @@ module Api
 
     def test_bbox_malformed
       @badmalformedbbox.each do |bbox|
-        get trackpoints_path(:bbox => bbox)
+        get api_tracepoints_path(:bbox => bbox)
         assert_response :bad_request, "The bbox:#{bbox} was expected to be malformed"
         assert_equal "The parameter bbox must be of the form min_lon,min_lat,max_lon,max_lat", @response.body, "bbox: #{bbox}"
       end
@@ -135,7 +135,7 @@ module Api
 
     def test_bbox_lon_mixedup
       @badlonmixedbbox.each do |bbox|
-        get trackpoints_path(:bbox => bbox)
+        get api_tracepoints_path(:bbox => bbox)
         assert_response :bad_request, "The bbox:#{bbox} was expected to have the longitude mixed up"
         assert_equal "The minimum longitude must be less than the maximum longitude, but it wasn't", @response.body, "bbox: #{bbox}"
       end
@@ -143,7 +143,7 @@ module Api
 
     def test_bbox_lat_mixedup
       @badlatmixedbbox.each do |bbox|
-        get trackpoints_path(:bbox => bbox)
+        get api_tracepoints_path(:bbox => bbox)
         assert_response :bad_request, "The bbox:#{bbox} was expected to have the latitude mixed up"
         assert_equal "The minimum latitude must be less than the maximum latitude, but it wasn't", @response.body, "bbox: #{bbox}"
       end
@@ -153,7 +153,7 @@ module Api
     def test_lat_lon_xml_format
       create(:tracepoint, :latitude => (0.00004 * GeoRecord::SCALE).to_i, :longitude => (0.00008 * GeoRecord::SCALE).to_i)
 
-      get trackpoints_path(:bbox => "0,0,0.1,0.1")
+      get api_tracepoints_path(:bbox => "0,0,0.1,0.1")
       assert_match(/lat="0.0000400"/, response.body)
       assert_match(/lon="0.0000800"/, response.body)
     end
index a486e4b5ee87f98f955fe1de82d5647ec93d4cc1..9bbca2ab170c7e0cfecd8b2536d8e96c446cb12a 100644 (file)
@@ -190,8 +190,8 @@ class ChangesetsControllerTest < ActionDispatch::IntegrationTest
   # Checks the display of the friends changesets listing
   def test_index_friends
     private_user = create(:user, :data_public => true)
-    friendship = create(:friendship, :befriender => private_user)
-    changeset = create(:changeset, :user => friendship.befriendee, :num_changes => 1)
+    follow = create(:follow, :follower => private_user)
+    changeset = create(:changeset, :user => follow.following, :num_changes => 1)
     _changeset2 = create(:changeset, :user => create(:user), :num_changes => 1)
 
     get friend_changesets_path
index a1c22fff8237e4dc356aca4f9e15dc792d4c4c95..c96c433bfc7e7824e45ed7338d720f54cb6ced38 100644 (file)
@@ -384,8 +384,8 @@ class DiaryEntriesControllerTest < ActionDispatch::IntegrationTest
   def test_index_friends
     user = create(:user)
     other_user = create(:user)
-    friendship = create(:friendship, :befriender => user)
-    diary_entry = create(:diary_entry, :user => friendship.befriendee)
+    follow = create(:follow, :follower => user)
+    diary_entry = create(:diary_entry, :user => follow.following)
     _other_entry = create(:diary_entry, :user => other_user)
 
     # Try a list of diary entries for your friends when not logged in
diff --git a/test/controllers/follows_controller_test.rb b/test/controllers/follows_controller_test.rb
new file mode 100644 (file)
index 0000000..93bb3bc
--- /dev/null
@@ -0,0 +1,182 @@
+require "test_helper"
+
+class FollowsControllerTest < ActionDispatch::IntegrationTest
+  ##
+  # test all routes which lead to this controller
+  def test_routes
+    assert_routing(
+      { :path => "/user/username/follow", :method => :get },
+      { :controller => "follows", :action => "show", :display_name => "username" }
+    )
+    assert_routing(
+      { :path => "/user/username/follow", :method => :post },
+      { :controller => "follows", :action => "create", :display_name => "username" }
+    )
+    assert_routing(
+      { :path => "/user/username/follow", :method => :delete },
+      { :controller => "follows", :action => "destroy", :display_name => "username" }
+    )
+  end
+
+  def test_follow
+    # Get users to work with
+    user = create(:user)
+    follow = create(:user)
+
+    # Check that the users aren't already friends
+    assert_nil Follow.find_by(:follower => user, :following => follow)
+
+    # When not logged in a GET should ask us to login
+    get follow_path(follow)
+    assert_redirected_to login_path(:referer => follow_path(follow))
+
+    # When not logged in a POST should error
+    post follow_path(follow)
+    assert_response :forbidden
+    assert_nil Follow.find_by(:follower => user, :following => follow)
+
+    session_for(user)
+
+    # When logged in a GET should get a confirmation page
+    get follow_path(follow)
+    assert_response :success
+    assert_template :show
+    assert_select "a[href*='test']", 0
+    assert_nil Follow.find_by(:follower => user, :following => follow)
+
+    # When logged in a POST should add the follow
+    assert_difference "ActionMailer::Base.deliveries.size", 1 do
+      perform_enqueued_jobs do
+        post follow_path(follow)
+      end
+    end
+    assert_redirected_to user_path(follow)
+    assert_match(/You are now following/, flash[:notice])
+    assert Follow.find_by(:follower => user, :following => follow)
+    email = ActionMailer::Base.deliveries.first
+    assert_equal 1, email.to.count
+    assert_equal follow.email, email.to.first
+    ActionMailer::Base.deliveries.clear
+
+    # A second POST should report that the follow already exists
+    assert_no_difference "ActionMailer::Base.deliveries.size" do
+      perform_enqueued_jobs do
+        post follow_path(follow)
+      end
+    end
+    assert_redirected_to user_path(follow)
+    assert_match(/You already follow/, flash[:warning])
+    assert Follow.find_by(:follower => user, :following => follow)
+  end
+
+  def test_follow_with_referer
+    # Get users to work with
+    user = create(:user)
+    follow = create(:user)
+    session_for(user)
+
+    # Check that the users aren't already friends
+    assert_nil Follow.find_by(:follower => user, :following => follow)
+
+    # The GET should preserve any referer
+    get follow_path(follow), :params => { :referer => "/test" }
+    assert_response :success
+    assert_template :show
+    assert_select "a[href*='test']"
+    assert_nil Follow.find_by(:follower => user, :following => follow)
+
+    # When logged in a POST should add the follow and refer us
+    assert_difference "ActionMailer::Base.deliveries.size", 1 do
+      perform_enqueued_jobs do
+        post follow_path(follow), :params => { :referer => "/test" }
+      end
+    end
+    assert_redirected_to "/test"
+    assert_match(/You are now following/, flash[:notice])
+    assert Follow.find_by(:follower => user, :following => follow)
+    email = ActionMailer::Base.deliveries.first
+    assert_equal 1, email.to.count
+    assert_equal follow.email, email.to.first
+    ActionMailer::Base.deliveries.clear
+  end
+
+  def test_follow_unknown_user
+    # Should error when a bogus user is specified
+    session_for(create(:user))
+    get follow_path("No Such User")
+    assert_response :not_found
+    assert_template :no_such_user
+  end
+
+  def test_unfollow
+    # Get users to work with
+    user = create(:user)
+    follow = create(:user)
+    create(:follow, :follower => user, :following => follow)
+
+    # Check that the users are friends
+    assert Follow.find_by(:follower => user, :following => follow)
+
+    # When not logged in a GET should ask us to login
+    get follow_path(follow)
+    assert_redirected_to login_path(:referer => follow_path(follow))
+
+    # When not logged in a POST should error
+    delete follow_path, :params => { :display_name => follow.display_name }
+    assert_response :forbidden
+    assert Follow.find_by(:follower => user, :following => follow)
+
+    session_for(user)
+
+    # When logged in a GET should get a confirmation page
+    get follow_path(follow)
+    assert_response :success
+    assert_template :show
+    assert_select "a[href*='test']", 0
+    assert Follow.find_by(:follower => user, :following => follow)
+
+    # When logged in a DELETE should remove the follow
+    delete follow_path(follow)
+    assert_redirected_to user_path(follow)
+    assert_match(/You successfully unfollowed/, flash[:notice])
+    assert_nil Follow.find_by(:follower => user, :following => follow)
+
+    # A second DELETE should report that the follow does not exist
+    delete follow_path(follow)
+    assert_redirected_to user_path(follow)
+    assert_match(/You are not following/, flash[:error])
+    assert_nil Follow.find_by(:follower => user, :following => follow)
+  end
+
+  def test_unfollow_with_referer
+    # Get users to work with
+    user = create(:user)
+    follow = create(:user)
+    create(:follow, :follower => user, :following => follow)
+    session_for(user)
+
+    # Check that the users are friends
+    assert Follow.find_by(:follower => user, :following => follow)
+
+    # The GET should preserve any referer
+    get follow_path(follow), :params => { :referer => "/test" }
+    assert_response :success
+    assert_template :show
+    assert_select "a[href*='test']"
+    assert Follow.find_by(:follower => user, :following => follow)
+
+    # When logged in a POST should remove the follow and refer
+    delete follow_path(follow), :params => { :referer => "/test" }
+    assert_redirected_to "/test"
+    assert_match(/You successfully unfollowed/, flash[:notice])
+    assert_nil Follow.find_by(:follower => user, :following => follow)
+  end
+
+  def test_unfollow_unknown_user
+    # Should error when a bogus user is specified
+    session_for(create(:user))
+    get follow_path("No Such User")
+    assert_response :not_found
+    assert_template :no_such_user
+  end
+end
diff --git a/test/controllers/friendships_controller_test.rb b/test/controllers/friendships_controller_test.rb
deleted file mode 100644 (file)
index f63a8e4..0000000
+++ /dev/null
@@ -1,198 +0,0 @@
-require "test_helper"
-
-class FriendshipsControllerTest < ActionDispatch::IntegrationTest
-  ##
-  # test all routes which lead to this controller
-  def test_routes
-    assert_routing(
-      { :path => "/user/username/make_friend", :method => :get },
-      { :controller => "friendships", :action => "make_friend", :display_name => "username" }
-    )
-    assert_routing(
-      { :path => "/user/username/make_friend", :method => :post },
-      { :controller => "friendships", :action => "make_friend", :display_name => "username" }
-    )
-    assert_routing(
-      { :path => "/user/username/remove_friend", :method => :get },
-      { :controller => "friendships", :action => "remove_friend", :display_name => "username" }
-    )
-    assert_routing(
-      { :path => "/user/username/remove_friend", :method => :post },
-      { :controller => "friendships", :action => "remove_friend", :display_name => "username" }
-    )
-  end
-
-  def test_make_friend
-    # Get users to work with
-    user = create(:user)
-    friend = create(:user)
-
-    # Check that the users aren't already friends
-    assert_nil Friendship.find_by(:befriender => user, :befriendee => friend)
-
-    # When not logged in a GET should ask us to login
-    get make_friend_path(friend)
-    assert_redirected_to login_path(:referer => make_friend_path(friend))
-
-    # When not logged in a POST should error
-    post make_friend_path(friend)
-    assert_response :forbidden
-    assert_nil Friendship.find_by(:befriender => user, :befriendee => friend)
-
-    session_for(user)
-
-    # When logged in a GET should get a confirmation page
-    get make_friend_path(friend)
-    assert_response :success
-    assert_template :make_friend
-    assert_select "form" do
-      assert_select "input[type='hidden'][name='referer']", 0
-      assert_select "input[type='submit']", 1
-    end
-    assert_nil Friendship.find_by(:befriender => user, :befriendee => friend)
-
-    # When logged in a POST should add the friendship
-    assert_difference "ActionMailer::Base.deliveries.size", 1 do
-      perform_enqueued_jobs do
-        post make_friend_path(friend)
-      end
-    end
-    assert_redirected_to user_path(friend)
-    assert_match(/is now your friend/, flash[:notice])
-    assert Friendship.find_by(:befriender => user, :befriendee => friend)
-    email = ActionMailer::Base.deliveries.first
-    assert_equal 1, email.to.count
-    assert_equal friend.email, email.to.first
-    ActionMailer::Base.deliveries.clear
-
-    # A second POST should report that the friendship already exists
-    assert_no_difference "ActionMailer::Base.deliveries.size" do
-      perform_enqueued_jobs do
-        post make_friend_path(friend)
-      end
-    end
-    assert_redirected_to user_path(friend)
-    assert_match(/You are already friends with/, flash[:warning])
-    assert Friendship.find_by(:befriender => user, :befriendee => friend)
-  end
-
-  def test_make_friend_with_referer
-    # Get users to work with
-    user = create(:user)
-    friend = create(:user)
-    session_for(user)
-
-    # Check that the users aren't already friends
-    assert_nil Friendship.find_by(:befriender => user, :befriendee => friend)
-
-    # The GET should preserve any referer
-    get make_friend_path(friend), :params => { :referer => "/test" }
-    assert_response :success
-    assert_template :make_friend
-    assert_select "form" do
-      assert_select "input[type='hidden'][name='referer'][value='/test']", 1
-      assert_select "input[type='submit']", 1
-    end
-    assert_nil Friendship.find_by(:befriender => user, :befriendee => friend)
-
-    # When logged in a POST should add the friendship and refer us
-    assert_difference "ActionMailer::Base.deliveries.size", 1 do
-      perform_enqueued_jobs do
-        post make_friend_path(friend), :params => { :referer => "/test" }
-      end
-    end
-    assert_redirected_to "/test"
-    assert_match(/is now your friend/, flash[:notice])
-    assert Friendship.find_by(:befriender => user, :befriendee => friend)
-    email = ActionMailer::Base.deliveries.first
-    assert_equal 1, email.to.count
-    assert_equal friend.email, email.to.first
-    ActionMailer::Base.deliveries.clear
-  end
-
-  def test_make_friend_unknown_user
-    # Should error when a bogus user is specified
-    session_for(create(:user))
-    get make_friend_path("No Such User")
-    assert_response :not_found
-    assert_template :no_such_user
-  end
-
-  def test_remove_friend
-    # Get users to work with
-    user = create(:user)
-    friend = create(:user)
-    create(:friendship, :befriender => user, :befriendee => friend)
-
-    # Check that the users are friends
-    assert Friendship.find_by(:befriender => user, :befriendee => friend)
-
-    # When not logged in a GET should ask us to login
-    get remove_friend_path(friend)
-    assert_redirected_to login_path(:referer => remove_friend_path(friend))
-
-    # When not logged in a POST should error
-    post remove_friend_path, :params => { :display_name => friend.display_name }
-    assert_response :forbidden
-    assert Friendship.find_by(:befriender => user, :befriendee => friend)
-
-    session_for(user)
-
-    # When logged in a GET should get a confirmation page
-    get remove_friend_path(friend)
-    assert_response :success
-    assert_template :remove_friend
-    assert_select "form" do
-      assert_select "input[type='hidden'][name='referer']", 0
-      assert_select "input[type='submit']", 1
-    end
-    assert Friendship.find_by(:befriender => user, :befriendee => friend)
-
-    # When logged in a POST should remove the friendship
-    post remove_friend_path(friend)
-    assert_redirected_to user_path(friend)
-    assert_match(/was removed from your friends/, flash[:notice])
-    assert_nil Friendship.find_by(:befriender => user, :befriendee => friend)
-
-    # A second POST should report that the friendship does not exist
-    post remove_friend_path(friend)
-    assert_redirected_to user_path(friend)
-    assert_match(/is not one of your friends/, flash[:error])
-    assert_nil Friendship.find_by(:befriender => user, :befriendee => friend)
-  end
-
-  def test_remove_friend_with_referer
-    # Get users to work with
-    user = create(:user)
-    friend = create(:user)
-    create(:friendship, :befriender => user, :befriendee => friend)
-    session_for(user)
-
-    # Check that the users are friends
-    assert Friendship.find_by(:befriender => user, :befriendee => friend)
-
-    # The GET should preserve any referer
-    get remove_friend_path(friend), :params => { :referer => "/test" }
-    assert_response :success
-    assert_template :remove_friend
-    assert_select "form" do
-      assert_select "input[type='hidden'][name='referer'][value='/test']", 1
-      assert_select "input[type='submit']", 1
-    end
-    assert Friendship.find_by(:befriender => user, :befriendee => friend)
-
-    # When logged in a POST should remove the friendship and refer
-    post remove_friend_path(friend), :params => { :referer => "/test" }
-    assert_redirected_to "/test"
-    assert_match(/was removed from your friends/, flash[:notice])
-    assert_nil Friendship.find_by(:befriender => user, :befriendee => friend)
-  end
-
-  def test_remove_friend_unknown_user
-    # Should error when a bogus user is specified
-    session_for(create(:user))
-    get remove_friend_path("No Such User")
-    assert_response :not_found
-    assert_template :no_such_user
-  end
-end
index 3a9088401e7e77dad6ff80d1ed1d08ac1475afa0..584d00ce859992958c3749f97e0218abfdb85085 100644 (file)
@@ -56,7 +56,7 @@ class Oauth2AuthorizedApplicationsControllerTest < ActionDispatch::IntegrationTe
       assert_select "li", :count => 3
       assert_select "li", :text => "Read user preferences"
       assert_select "li", :text => "Modify user preferences"
-      assert_select "li", :text => "Create diary entries, comments and make friends"
+      assert_select "li", :text => "Create diary entries and comments"
     end
   end
 
diff --git a/test/controllers/traces/feeds_controller_test.rb b/test/controllers/traces/feeds_controller_test.rb
new file mode 100644 (file)
index 0000000..b0642bf
--- /dev/null
@@ -0,0 +1,109 @@
+require "test_helper"
+
+module Traces
+  class FeedsControllerTest < ActionDispatch::IntegrationTest
+    ##
+    # test all routes which lead to this controller
+    def test_routes
+      assert_routing(
+        { :path => "/traces/rss", :method => :get },
+        { :controller => "traces/feeds", :action => "show", :format => :rss }
+      )
+      assert_routing(
+        { :path => "/traces/tag/tagname/rss", :method => :get },
+        { :controller => "traces/feeds", :action => "show", :tag => "tagname", :format => :rss }
+      )
+      assert_routing(
+        { :path => "/user/username/traces/rss", :method => :get },
+        { :controller => "traces/feeds", :action => "show", :display_name => "username", :format => :rss }
+      )
+      assert_routing(
+        { :path => "/user/username/traces/tag/tagname/rss", :method => :get },
+        { :controller => "traces/feeds", :action => "show", :display_name => "username", :tag => "tagname", :format => :rss }
+      )
+    end
+
+    def test_show
+      user = create(:user)
+      # The fourth test below is surprisingly sensitive to timestamp ordering when the timestamps are equal.
+      trace_a = create(:trace, :visibility => "public", :timestamp => 4.seconds.ago) do |trace|
+        create(:tracetag, :trace => trace, :tag => "London")
+      end
+      trace_b = create(:trace, :visibility => "public", :timestamp => 3.seconds.ago) do |trace|
+        create(:tracetag, :trace => trace, :tag => "Birmingham")
+      end
+      create(:trace, :visibility => "private", :user => user, :timestamp => 2.seconds.ago) do |trace|
+        create(:tracetag, :trace => trace, :tag => "London")
+      end
+      create(:trace, :visibility => "private", :user => user, :timestamp => 1.second.ago) do |trace|
+        create(:tracetag, :trace => trace, :tag => "Birmingham")
+      end
+
+      # First with the public feed
+      get traces_feed_path
+      check_trace_feed [trace_b, trace_a]
+
+      # Restrict traces to those with a given tag
+      get traces_feed_path(:tag => "London")
+      check_trace_feed [trace_a]
+    end
+
+    def test_show_user
+      user = create(:user)
+      second_user = create(:user)
+      create(:user)
+      create(:trace)
+      trace_b = create(:trace, :visibility => "public", :timestamp => 4.seconds.ago, :user => user)
+      trace_c = create(:trace, :visibility => "public", :timestamp => 3.seconds.ago, :user => user) do |trace|
+        create(:tracetag, :trace => trace, :tag => "London")
+      end
+      create(:trace, :visibility => "private")
+
+      # Test a user with no traces
+      get traces_feed_path(:display_name => second_user)
+      check_trace_feed []
+
+      # Test the user with the traces - should see only public ones
+      get traces_feed_path(:display_name => user)
+      check_trace_feed [trace_c, trace_b]
+
+      # Should only see traces with the correct tag when a tag is specified
+      get traces_feed_path(:display_name => user, :tag => "London")
+      check_trace_feed [trace_c]
+
+      # Should no traces if the user does not exist
+      get traces_feed_path(:display_name => "UnknownUser")
+      check_trace_feed []
+    end
+
+    private
+
+    def check_trace_feed(traces)
+      assert_response :success
+      assert_template "traces/feeds/show"
+      assert_equal "application/rss+xml", @response.media_type
+      assert_select "rss", :count => 1 do
+        assert_select "channel", :count => 1 do
+          assert_select "title"
+          assert_select "description"
+          assert_select "link"
+          assert_select "image"
+          assert_select "item", :count => traces.length do |items|
+            traces.zip(items).each do |trace, item|
+              assert_select item, "title", trace.name
+              assert_select item, "link", "http://www.example.com/user/#{ERB::Util.u(trace.user.display_name)}/traces/#{trace.id}"
+              assert_select item, "guid", "http://www.example.com/user/#{ERB::Util.u(trace.user.display_name)}/traces/#{trace.id}"
+              assert_select item, "description" do
+                assert_dom_encoded do
+                  assert_select "img[src='#{trace_icon_url trace.user, trace}']"
+                end
+              end
+              # assert_select item, "dc:creator", trace.user.display_name
+              assert_select item, "pubDate", trace.timestamp.rfc822
+            end
+          end
+        end
+      end
+    end
+  end
+end
index c5f503d6299f5a8814b024b47cca24ed9af00847..78d714289deebec6d6ecf8a0912ce049333d5c3d 100644 (file)
@@ -30,23 +30,6 @@ class TracesControllerTest < ActionDispatch::IntegrationTest
       { :controller => "traces", :action => "mine", :tag => "tagname" }
     )
 
-    assert_routing(
-      { :path => "/traces/rss", :method => :get },
-      { :controller => "traces", :action => "georss", :format => :rss }
-    )
-    assert_routing(
-      { :path => "/traces/tag/tagname/rss", :method => :get },
-      { :controller => "traces", :action => "georss", :tag => "tagname", :format => :rss }
-    )
-    assert_routing(
-      { :path => "/user/username/traces/rss", :method => :get },
-      { :controller => "traces", :action => "georss", :display_name => "username", :format => :rss }
-    )
-    assert_routing(
-      { :path => "/user/username/traces/tag/tagname/rss", :method => :get },
-      { :controller => "traces", :action => "georss", :display_name => "username", :tag => "tagname", :format => :rss }
-    )
-
     assert_routing(
       { :path => "/user/username/traces/1", :method => :get },
       { :controller => "traces", :action => "show", :display_name => "username", :id => "1" }
@@ -333,61 +316,6 @@ class TracesControllerTest < ActionDispatch::IntegrationTest
     end
   end
 
-  # Check the RSS feed
-  def test_rss
-    user = create(:user)
-    # The fourth test below is surprisingly sensitive to timestamp ordering when the timestamps are equal.
-    trace_a = create(:trace, :visibility => "public", :timestamp => 4.seconds.ago) do |trace|
-      create(:tracetag, :trace => trace, :tag => "London")
-    end
-    trace_b = create(:trace, :visibility => "public", :timestamp => 3.seconds.ago) do |trace|
-      create(:tracetag, :trace => trace, :tag => "Birmingham")
-    end
-    create(:trace, :visibility => "private", :user => user, :timestamp => 2.seconds.ago) do |trace|
-      create(:tracetag, :trace => trace, :tag => "London")
-    end
-    create(:trace, :visibility => "private", :user => user, :timestamp => 1.second.ago) do |trace|
-      create(:tracetag, :trace => trace, :tag => "Birmingham")
-    end
-
-    # First with the public feed
-    get traces_rss_path
-    check_trace_feed [trace_b, trace_a]
-
-    # Restrict traces to those with a given tag
-    get traces_rss_path(:tag => "London")
-    check_trace_feed [trace_a]
-  end
-
-  # Check the RSS feed for a specific user
-  def test_rss_user
-    user = create(:user)
-    second_user = create(:user)
-    create(:user)
-    create(:trace)
-    trace_b = create(:trace, :visibility => "public", :timestamp => 4.seconds.ago, :user => user)
-    trace_c = create(:trace, :visibility => "public", :timestamp => 3.seconds.ago, :user => user) do |trace|
-      create(:tracetag, :trace => trace, :tag => "London")
-    end
-    create(:trace, :visibility => "private")
-
-    # Test a user with no traces
-    get traces_rss_path(:display_name => second_user.display_name)
-    check_trace_feed []
-
-    # Test the user with the traces - should see only public ones
-    get traces_rss_path(:display_name => user.display_name)
-    check_trace_feed [trace_c, trace_b]
-
-    # Should only see traces with the correct tag when a tag is specified
-    get traces_rss_path(:display_name => user.display_name, :tag => "London")
-    check_trace_feed [trace_c]
-
-    # Should no traces if the user does not exist
-    get traces_rss_path(:display_name => "UnknownUser")
-    check_trace_feed []
-  end
-
   # Test showing a trace
   def test_show
     public_trace_file = create(:trace, :visibility => "public")
@@ -721,34 +649,6 @@ class TracesControllerTest < ActionDispatch::IntegrationTest
 
   private
 
-  def check_trace_feed(traces)
-    assert_response :success
-    assert_template "georss"
-    assert_equal "application/rss+xml", @response.media_type
-    assert_select "rss", :count => 1 do
-      assert_select "channel", :count => 1 do
-        assert_select "title"
-        assert_select "description"
-        assert_select "link"
-        assert_select "image"
-        assert_select "item", :count => traces.length do |items|
-          traces.zip(items).each do |trace, item|
-            assert_select item, "title", trace.name
-            assert_select item, "link", "http://www.example.com/user/#{ERB::Util.u(trace.user.display_name)}/traces/#{trace.id}"
-            assert_select item, "guid", "http://www.example.com/user/#{ERB::Util.u(trace.user.display_name)}/traces/#{trace.id}"
-            assert_select item, "description" do
-              assert_dom_encoded do
-                assert_select "img[src='#{trace_icon_url trace.user, trace}']"
-              end
-            end
-            # assert_select item, "dc:creator", trace.user.display_name
-            assert_select item, "pubDate", trace.timestamp.rfc822
-          end
-        end
-      end
-    end
-  end
-
   def check_trace_index(traces)
     assert_response :success
     assert_template "index"
diff --git a/test/controllers/users/statuses_controller_test.rb b/test/controllers/users/statuses_controller_test.rb
new file mode 100644 (file)
index 0000000..9cdcfc8
--- /dev/null
@@ -0,0 +1,64 @@
+require "test_helper"
+
+module Users
+  class StatusesControllerTest < ActionDispatch::IntegrationTest
+    ##
+    # test all routes which lead to this controller
+    def test_routes
+      assert_routing(
+        { :path => "/user/username/status", :method => :put },
+        { :controller => "users/statuses", :action => "update", :user_display_name => "username" }
+      )
+    end
+
+    def test_update
+      user = create(:user)
+
+      # Try without logging in
+      put user_status_path(user, :event => "confirm")
+      assert_response :forbidden
+
+      # Now try as a normal user
+      session_for(user)
+      put user_status_path(user, :event => "confirm")
+      assert_redirected_to :controller => "/errors", :action => :forbidden
+
+      # Finally try as an administrator
+      session_for(create(:administrator_user))
+      put user_status_path(user, :event => "confirm")
+      assert_redirected_to user_path(user)
+      assert_equal "confirmed", User.find(user.id).status
+    end
+
+    def test_destroy
+      user = create(:user, :home_lat => 12.1, :home_lon => 12.1, :description => "test")
+
+      # Try without logging in
+      put user_status_path(user, :event => "soft_destroy")
+      assert_response :forbidden
+
+      # Now try as a normal user
+      session_for(user)
+      put user_status_path(user, :event => "soft_destroy")
+      assert_redirected_to :controller => "/errors", :action => :forbidden
+
+      # Finally try as an administrator
+      session_for(create(:administrator_user))
+      put user_status_path(user, :event => "soft_destroy")
+      assert_redirected_to user_path(user)
+
+      # Check that the user was deleted properly
+      user.reload
+      assert_equal "user_#{user.id}", user.display_name
+      assert_equal "", user.description
+      assert_nil user.home_lat
+      assert_nil user.home_lon
+      assert_not user.avatar.attached?
+      assert_not user.email_valid
+      assert_nil user.new_email
+      assert_nil user.auth_provider
+      assert_nil user.auth_uid
+      assert_equal "deleted", user.status
+    end
+  end
+end
index 9ffa6695d97ab80dc5327f3105cfeef4dac7a6e7..c5378ce50756ac90313fa42e86b0eb3839c73aca 100644 (file)
@@ -28,15 +28,6 @@ class UsersControllerTest < ActionDispatch::IntegrationTest
       { :path => "/user/username", :method => :get },
       { :controller => "users", :action => "show", :display_name => "username" }
     )
-
-    assert_routing(
-      { :path => "/user/username/set_status", :method => :post },
-      { :controller => "users", :action => "set_status", :display_name => "username" }
-    )
-    assert_routing(
-      { :path => "/user/username", :method => :delete },
-      { :controller => "users", :action => "destroy", :display_name => "username" }
-    )
   end
 
   # The user creation page loads
@@ -332,56 +323,6 @@ class UsersControllerTest < ActionDispatch::IntegrationTest
     end
   end
 
-  def test_set_status
-    user = create(:user)
-
-    # Try without logging in
-    post set_status_user_path(user), :params => { :event => "confirm" }
-    assert_response :forbidden
-
-    # Now try as a normal user
-    session_for(user)
-    post set_status_user_path(user), :params => { :event => "confirm" }
-    assert_redirected_to :controller => :errors, :action => :forbidden
-
-    # Finally try as an administrator
-    session_for(create(:administrator_user))
-    post set_status_user_path(user), :params => { :event => "confirm" }
-    assert_redirected_to :action => :show, :display_name => user.display_name
-    assert_equal "confirmed", User.find(user.id).status
-  end
-
-  def test_destroy
-    user = create(:user, :home_lat => 12.1, :home_lon => 12.1, :description => "test")
-
-    # Try without logging in
-    delete user_path(user)
-    assert_response :forbidden
-
-    # Now try as a normal user
-    session_for(user)
-    delete user_path(user)
-    assert_redirected_to :controller => :errors, :action => :forbidden
-
-    # Finally try as an administrator
-    session_for(create(:administrator_user))
-    delete user_path(user)
-    assert_redirected_to :action => :show, :display_name => user.display_name
-
-    # Check that the user was deleted properly
-    user.reload
-    assert_equal "user_#{user.id}", user.display_name
-    assert_equal "", user.description
-    assert_nil user.home_lat
-    assert_nil user.home_lon
-    assert_not user.avatar.attached?
-    assert_not user.email_valid
-    assert_nil user.new_email
-    assert_nil user.auth_provider
-    assert_nil user.auth_uid
-    assert_equal "deleted", user.status
-  end
-
   def test_auth_failure_callback
     get auth_failure_path
     assert_redirected_to login_path
diff --git a/test/factories/follows.rb b/test/factories/follows.rb
new file mode 100644 (file)
index 0000000..01dad52
--- /dev/null
@@ -0,0 +1,6 @@
+FactoryBot.define do
+  factory :follow do
+    follower :factory => :user
+    following :factory => :user
+  end
+end
diff --git a/test/factories/friendships.rb b/test/factories/friendships.rb
deleted file mode 100644 (file)
index 12df2a9..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-FactoryBot.define do
-  factory :friendship do
-    befriender :factory => :user
-    befriendee :factory => :user
-  end
-end
index 6836c4f70a0fc81e15215ce5fbb445723b8c69b1..5c1c5a26f2a37f2520a10d1f368743c088804681 100644 (file)
@@ -136,18 +136,18 @@ class UserTest < ActiveSupport::TestCase
     assert_predicate user, :valid?, "user_0 display_name is invalid but it hasn't been changed"
   end
 
-  def test_friends_with
+  def test_follows
     alice = create(:user, :active)
     bob = create(:user, :active)
     charlie = create(:user, :active)
-    create(:friendship, :befriender => alice, :befriendee => bob)
-
-    assert alice.friends_with?(bob)
-    assert_not alice.friends_with?(charlie)
-    assert_not bob.friends_with?(alice)
-    assert_not bob.friends_with?(charlie)
-    assert_not charlie.friends_with?(bob)
-    assert_not charlie.friends_with?(alice)
+    create(:follow, :follower => alice, :following => bob)
+
+    assert alice.follows?(bob)
+    assert_not alice.follows?(charlie)
+    assert_not bob.follows?(alice)
+    assert_not bob.follows?(charlie)
+    assert_not charlie.follows?(bob)
+    assert_not charlie.follows?(alice)
   end
 
   def test_users_nearby
@@ -174,13 +174,13 @@ class UserTest < ActiveSupport::TestCase
   def test_friends
     norm = create(:user, :active)
     sec = create(:user, :active)
-    create(:friendship, :befriender => norm, :befriendee => sec)
+    create(:follow, :follower => norm, :following => sec)
 
-    assert_equal [sec], norm.friends
-    assert_equal 1, norm.friends.size
+    assert_equal [sec], norm.followings
+    assert_equal 1, norm.followings.size
 
-    assert_empty sec.friends
-    assert_equal 0, sec.friends.size
+    assert_empty sec.followings
+    assert_equal 0, sec.followings.size
   end
 
   def test_user_preferred_editor
index bc2e3b4e974c79949603a5666260226d6e1afe93..12d9609383104d22402dce2c07279703f3219b7c 100644 (file)
@@ -1,27 +1,52 @@
 require "application_system_test_case"
 
 class DashboardSystemTest < ApplicationSystemTestCase
-  test "show no users if have no friends" do
+  test "show no users if have no followings" do
     user = create(:user)
     sign_in_as(user)
 
     visit dashboard_path
-    assert_text "You have not added any friends yet."
+    assert_text "You have not followed any user yet."
   end
 
   test "show users if have friends" do
     user = create(:user, :home_lon => 1.1, :home_lat => 1.1)
     friend_user = create(:user, :home_lon => 1.2, :home_lat => 1.2)
-    create(:friendship, :befriender => user, :befriendee => friend_user)
+    create(:follow, :follower => user, :following => friend_user)
     create(:changeset, :user => friend_user)
     sign_in_as(user)
 
     visit dashboard_path
-    assert_no_text "You have not added any friends yet."
+    assert_no_text "You have not followed any user yet."
 
-    friends_heading = find :element, "h2", :text => "My friends"
+    friends_heading = find :element, "h2", :text => "Followings"
     others_heading = find :element, "h2", :text => "Other nearby users"
 
     assert_link friend_user.display_name, :below => friends_heading, :above => others_heading
   end
+
+  test "show nearby users with ability to follow" do
+    user = create(:user, :home_lon => 1.1, :home_lat => 1.1)
+    nearby_user = create(:user, :home_lon => 1.2, :home_lat => 1.2)
+    sign_in_as(user)
+
+    visit dashboard_path
+
+    within_content_body do
+      others_nearby_heading = find :element, "h2", :text => "Other nearby users"
+
+      assert_no_text "There are no other users who admit to mapping nearby yet"
+      assert_link nearby_user.display_name, :below => others_nearby_heading
+      assert_link "Follow", :below => others_nearby_heading
+
+      click_on "Follow"
+
+      followings_heading = find :element, "h2", :text => "Followings"
+      others_nearby_heading = find :element, "h2", :text => "Other nearby users"
+
+      assert_text "There are no other users who admit to mapping nearby yet"
+      assert_link nearby_user.display_name, :below => followings_heading, :above => others_nearby_heading
+      assert_link "Unfollow", :below => followings_heading, :above => others_nearby_heading
+    end
+  end
 end
diff --git a/test/system/follows_test.rb b/test/system/follows_test.rb
new file mode 100644 (file)
index 0000000..176a594
--- /dev/null
@@ -0,0 +1,18 @@
+require "application_system_test_case"
+
+class FollowsTest < ApplicationSystemTestCase
+  test "show message when max frienships limit is exceeded" do
+    following = create(:user)
+
+    sign_in_as create(:user)
+
+    with_settings(:max_follows_per_hour => 0) do
+      visit user_path(following)
+      assert_link "Follow"
+
+      click_on "Follow"
+      assert_text "You have followed a lot of users recently"
+      assert_link "Follow"
+    end
+  end
+end
diff --git a/test/system/friendships_test.rb b/test/system/friendships_test.rb
deleted file mode 100644 (file)
index 1db4d3c..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-require "application_system_test_case"
-
-class FriendshipsTest < ApplicationSystemTestCase
-  test "show message when max frienships limit is exceeded" do
-    befriendee = create(:user)
-
-    sign_in_as create(:user)
-
-    with_settings(:max_friends_per_hour => 0) do
-      visit user_path(befriendee)
-      assert_link "Add Friend"
-
-      click_on "Add Friend"
-      assert_text "You have friended a lot of users recently"
-      assert_link "Add Friend"
-    end
-  end
-end