* Allow every controller to take the `search[id]` param. * Parse the `search[id]` param the same way that the `id:<N>` metatag is parsed. So `search[id]=1,2,3`, `search[id]=<42`, `search[id]=1..10`, for example, are all accepted.
246 lines
6.2 KiB
Ruby
246 lines
6.2 KiB
Ruby
class Note < ApplicationRecord
|
|
class RevertError < Exception ; end
|
|
|
|
attr_accessor :updater_id, :updater_ip_addr, :html_id
|
|
belongs_to :post
|
|
belongs_to :creator, :class_name => "User"
|
|
belongs_to :updater, :class_name => "User"
|
|
has_many :versions, lambda {order("note_versions.id ASC")}, :class_name => "NoteVersion", :dependent => :destroy
|
|
before_validation :initialize_creator, :on => :create
|
|
before_validation :initialize_updater
|
|
validates_presence_of :post_id, :creator_id, :updater_id, :x, :y, :width, :height, :body
|
|
validate :post_must_exist
|
|
validate :note_within_image
|
|
after_save :update_post
|
|
after_save :create_version
|
|
validate :post_must_not_be_note_locked
|
|
attr_accessible :x, :y, :width, :height, :body, :updater_id, :updater_ip_addr, :is_active, :post_id, :post, :html_id
|
|
|
|
module SearchMethods
|
|
def active
|
|
where("is_active = TRUE")
|
|
end
|
|
|
|
def body_matches(query)
|
|
if query =~ /\*/ && CurrentUser.user.is_builder?
|
|
where("body ILIKE ? ESCAPE E'\\\\'", query.to_escaped_for_sql_like)
|
|
else
|
|
where("body_index @@ plainto_tsquery(E?)", query.to_escaped_for_tsquery_split)
|
|
end
|
|
end
|
|
|
|
def post_tags_match(query)
|
|
PostQueryBuilder.new(query).build(self.joins(:post)).reorder("")
|
|
end
|
|
|
|
def for_creator(user_id)
|
|
where("creator_id = ?", user_id)
|
|
end
|
|
|
|
def creator_name(name)
|
|
where("creator_id = (select _.id from users _ where lower(_.name) = ?)", name.mb_chars.downcase)
|
|
end
|
|
|
|
def search(params)
|
|
q = super
|
|
return q if params.blank?
|
|
|
|
if params[:body_matches].present?
|
|
q = q.body_matches(params[:body_matches])
|
|
end
|
|
|
|
if params[:is_active] == "true"
|
|
q = q.active
|
|
elsif params[:is_active] == "false"
|
|
q = q.where("is_active = false")
|
|
end
|
|
|
|
if params[:post_id].present?
|
|
q = q.where(post_id: params[:post_id].split(",").map(&:to_i))
|
|
end
|
|
|
|
if params[:post_tags_match].present?
|
|
q = q.post_tags_match(params[:post_tags_match])
|
|
end
|
|
|
|
if params[:creator_name].present?
|
|
q = q.creator_name(params[:creator_name].tr(" ", "_"))
|
|
end
|
|
|
|
if params[:creator_id].present?
|
|
q = q.where(creator_id: params[:creator_id].split(",").map(&:to_i))
|
|
end
|
|
|
|
q
|
|
end
|
|
end
|
|
|
|
module ApiMethods
|
|
def hidden_attributes
|
|
super + [:body_index]
|
|
end
|
|
|
|
def method_attributes
|
|
super + [:creator_name]
|
|
end
|
|
end
|
|
|
|
extend SearchMethods
|
|
include ApiMethods
|
|
|
|
def initialize_creator
|
|
self.creator_id ||= CurrentUser.id
|
|
end
|
|
|
|
def initialize_updater
|
|
self.updater_id = CurrentUser.id
|
|
self.updater_ip_addr = CurrentUser.ip_addr
|
|
end
|
|
|
|
def post_must_exist
|
|
if !Post.exists?(post_id)
|
|
errors.add :post, "must exist"
|
|
return false
|
|
end
|
|
end
|
|
|
|
def post_must_not_be_note_locked
|
|
if is_locked?
|
|
errors.add :post, "is note locked"
|
|
return false
|
|
end
|
|
end
|
|
|
|
def note_within_image
|
|
return false unless post.present?
|
|
if x < 0 || y < 0 || (x > post.image_width) || (y > post.image_height) || width < 0 || height < 0 || (x + width > post.image_width) || (y + height > post.image_height)
|
|
self.errors.add(:note, "must be inside the image")
|
|
return false
|
|
end
|
|
end
|
|
|
|
def is_locked?
|
|
Post.exists?(["id = ? AND is_note_locked = ?", post_id, true])
|
|
end
|
|
|
|
def creator_name
|
|
User.id_to_name(creator_id)
|
|
end
|
|
|
|
def rescale!(x_scale, y_scale)
|
|
self.x *= x_scale
|
|
self.y *= y_scale
|
|
self.width *= x_scale
|
|
self.height *= y_scale
|
|
save!
|
|
end
|
|
|
|
def update_post
|
|
if self.changed?
|
|
if Note.where(:is_active => true, :post_id => post_id).exists?
|
|
execute_sql("UPDATE posts SET last_noted_at = ? WHERE id = ?", updated_at, post_id)
|
|
else
|
|
execute_sql("UPDATE posts SET last_noted_at = NULL WHERE id = ?", post_id)
|
|
end
|
|
end
|
|
end
|
|
|
|
def create_version
|
|
return unless versioned_attributes_changed?
|
|
|
|
if merge_version?
|
|
merge_version
|
|
else
|
|
Note.where(:id => id).update_all("version = coalesce(version, 0) + 1")
|
|
reload
|
|
create_new_version
|
|
end
|
|
end
|
|
|
|
def versioned_attributes_changed?
|
|
new_record? || x_changed? || y_changed? || width_changed? || height_changed? || is_active_changed? || body_changed?
|
|
end
|
|
|
|
def create_new_version
|
|
versions.create(
|
|
:updater_id => updater_id,
|
|
:updater_ip_addr => updater_ip_addr,
|
|
:post_id => post_id,
|
|
:x => x,
|
|
:y => y,
|
|
:width => width,
|
|
:height => height,
|
|
:is_active => is_active,
|
|
:body => body,
|
|
:version => version
|
|
)
|
|
end
|
|
|
|
def merge_version
|
|
prev = versions.last
|
|
prev.update_attributes(
|
|
:x => x,
|
|
:y => y,
|
|
:width => width,
|
|
:height => height,
|
|
:is_active => is_active,
|
|
:body => body
|
|
)
|
|
end
|
|
|
|
def merge_version?
|
|
prev = versions.last
|
|
prev && prev.updater_id == CurrentUser.user.id && prev.updated_at > 1.hour.ago && !is_active_changed?
|
|
end
|
|
|
|
def revert_to(version)
|
|
if id != version.note_id
|
|
raise RevertError.new("You cannot revert to a previous version of another note.")
|
|
end
|
|
|
|
self.x = version.x
|
|
self.y = version.y
|
|
self.post_id = version.post_id
|
|
self.body = version.body
|
|
self.width = version.width
|
|
self.height = version.height
|
|
self.is_active = version.is_active
|
|
self.updater_id = CurrentUser.id
|
|
self.updater_ip_addr = CurrentUser.ip_addr
|
|
end
|
|
|
|
def revert_to!(version)
|
|
revert_to(version)
|
|
save!
|
|
end
|
|
|
|
def copy_to(new_post)
|
|
new_note = dup
|
|
new_note.post_id = new_post.id
|
|
new_note.version = 0
|
|
|
|
width_ratio = new_post.image_width.to_f / post.image_width
|
|
height_ratio = new_post.image_height.to_f / post.image_height
|
|
new_note.x = x * width_ratio
|
|
new_note.y = y * height_ratio
|
|
new_note.width = width * width_ratio
|
|
new_note.height = height * height_ratio
|
|
|
|
new_note.save
|
|
end
|
|
|
|
def self.undo_changes_by_user(vandal_id)
|
|
transaction do
|
|
note_ids = NoteVersion.where(:updater_id => vandal_id).select("note_id").distinct.map(&:note_id)
|
|
NoteVersion.where(["updater_id = ?", vandal_id]).delete_all
|
|
note_ids.each do |note_id|
|
|
note = Note.find(note_id)
|
|
most_recent = note.versions.last
|
|
if most_recent
|
|
note.revert_to!(most_recent)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|