このコミットが含まれているのは:
@@ -57,7 +57,7 @@ class Post < ApplicationRecord
|
||||
|
||||
attribute :version_no, :integer, default: 1
|
||||
|
||||
before_validation :normalise_url
|
||||
before_validation :normalise_url, if: :will_save_change_to_url?
|
||||
|
||||
validates :url, presence: true, uniqueness: true
|
||||
validates :video_ms, numericality: { only_integer: true, greater_than: 0 }, allow_nil: true
|
||||
@@ -180,7 +180,7 @@ class Post < ApplicationRecord
|
||||
|
||||
u.host = u.host.downcase if u.host
|
||||
u.path = u.path.sub(/\/\Z/, '') if u.path.present?
|
||||
self.url = u.to_s
|
||||
self.url = PostUrlSanitisationRule.sanitise(u.to_s)
|
||||
rescue URI::InvalidURIError
|
||||
;
|
||||
end
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
class PostUrlSanitisationRule < ApplicationRecord
|
||||
include Discard::Model
|
||||
|
||||
class UrlConflictError < StandardError
|
||||
attr_reader :conflicts
|
||||
|
||||
def initialize(conflicts)
|
||||
@conflicts = conflicts
|
||||
urls = conflicts.map { _1.fetch(:url) }.uniq.join(', ')
|
||||
super("post URL sanitisation conflicts detected for #{ urls }")
|
||||
end
|
||||
end
|
||||
|
||||
self.primary_key = :priority
|
||||
|
||||
default_scope -> { kept }
|
||||
|
||||
validates :source_pattern, presence: true, uniqueness: true
|
||||
|
||||
validate :source_pattern_must_be_regexp
|
||||
|
||||
class << self
|
||||
def sanitise(url) =
|
||||
rules.reduce(url.dup) { |value, (pattern, replacement)| value.sub(pattern, replacement) }
|
||||
|
||||
def apply!
|
||||
rewrites = Post.order(:id).pluck(:id, :url).map do |post_id, original_url|
|
||||
{ post_id:,
|
||||
original_url:,
|
||||
sanitised_url: sanitise(original_url) }
|
||||
end
|
||||
|
||||
conflicts = rewrites
|
||||
.group_by { _1.fetch(:sanitised_url) }
|
||||
.values
|
||||
.filter { _1.size > 1 }
|
||||
.flatten
|
||||
.map { { url: _1.fetch(:sanitised_url),
|
||||
post_id: _1.fetch(:post_id),
|
||||
original_url: _1.fetch(:original_url) } }
|
||||
|
||||
raise UrlConflictError.new(conflicts) if conflicts.present?
|
||||
|
||||
changed = rewrites.filter { _1.fetch(:original_url) != _1.fetch(:sanitised_url) }
|
||||
return if changed.empty?
|
||||
|
||||
token = SecureRandom.hex(6)
|
||||
|
||||
Post.transaction do
|
||||
changed.each do |row|
|
||||
Post.where(id: row.fetch(:post_id))
|
||||
.update_all(url: temporary_url_for(row.fetch(:post_id), token))
|
||||
end
|
||||
|
||||
changed.each do |row|
|
||||
Post.where(id: row.fetch(:post_id))
|
||||
.update_all(url: row.fetch(:sanitised_url))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def rules = kept.order(:priority).map { |r| [Regexp.new(r.source_pattern), r.replacement] }
|
||||
|
||||
def temporary_url_for(post_id, token) =
|
||||
"https://post-url-sanitising.invalid/#{ token }/#{ post_id }"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def source_pattern_must_be_regexp
|
||||
Regexp.new(source_pattern)
|
||||
rescue RegexpError
|
||||
errors.add :source_pattern, '変な正規表現だね〜(笑)'
|
||||
end
|
||||
end
|
||||
新しい課題から参照
ユーザをブロックする