From 7d02bcd2e0165a90a9f2c1edb34b064ff76afd69 Mon Sep 17 00:00:00 2001 From: Michael Kozono Date: Mon, 1 May 2017 13:46:30 -0700 Subject: Redirect from redirect routes to canonical routes --- app/models/concerns/routable.rb | 24 ++++++++++++++++++++---- app/models/redirect_route.rb | 10 ++++++++++ app/models/user.rb | 5 +++++ 3 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 app/models/redirect_route.rb (limited to 'app/models') diff --git a/app/models/concerns/routable.rb b/app/models/concerns/routable.rb index b28e05d0c28..e351dbb45dd 100644 --- a/app/models/concerns/routable.rb +++ b/app/models/concerns/routable.rb @@ -5,6 +5,7 @@ module Routable included do has_one :route, as: :source, autosave: true, dependent: :destroy + has_many :redirect_routes, as: :source, autosave: true, dependent: :destroy validates_associated :route validates :route, presence: true @@ -26,16 +27,31 @@ module Routable # Klass.find_by_full_path('gitlab-org/gitlab-ce') # # Returns a single object, or nil. - def find_by_full_path(path) + def find_by_full_path(path, follow_redirects: false) # On MySQL we want to ensure the ORDER BY uses a case-sensitive match so # any literal matches come first, for this we have to use "BINARY". # Without this there's still no guarantee in what order MySQL will return # rows. + # + # Why do we do this? + # + # Even though we have Rails validation on Route for unique paths + # (case-insensitive), there are old projects in our DB (and possibly + # clients' DBs) that have the same path with different cases. + # See https://gitlab.com/gitlab-org/gitlab-ce/issues/18603. Also note that + # our unique index is case-sensitive in Postgres. binary = Gitlab::Database.mysql? ? 'BINARY' : '' - order_sql = "(CASE WHEN #{binary} routes.path = #{connection.quote(path)} THEN 0 ELSE 1 END)" - - where_full_path_in([path]).reorder(order_sql).take + found = where_full_path_in([path]).reorder(order_sql).take + return found if found + + if follow_redirects + if Gitlab::Database.postgresql? + joins(:redirect_routes).find_by("LOWER(redirect_routes.path) = LOWER(?)", path) + else + joins(:redirect_routes).find_by(path: path) + end + end end # Builds a relation to find multiple objects by their full paths. diff --git a/app/models/redirect_route.rb b/app/models/redirect_route.rb new file mode 100644 index 00000000000..e36ca988701 --- /dev/null +++ b/app/models/redirect_route.rb @@ -0,0 +1,10 @@ +class RedirectRoute < ActiveRecord::Base + belongs_to :source, polymorphic: true + + validates :source, presence: true + + validates :path, + length: { within: 1..255 }, + presence: true, + uniqueness: { case_sensitive: false } +end diff --git a/app/models/user.rb b/app/models/user.rb index 43c5fdc038d..c6354b989ad 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -333,6 +333,11 @@ class User < ActiveRecord::Base find_by(id: Key.unscoped.select(:user_id).where(id: key_id)) end + def find_by_full_path(path, follow_redirects: false) + namespace = Namespace.find_by_full_path(path, follow_redirects: follow_redirects) + namespace.owner if namespace && namespace.owner + end + def reference_prefix '@' end -- cgit v1.2.3