fixed tag and pending post tests, added category multiget helper to tag, post/unapproval/post version in progress still
This commit is contained in:
@@ -2,8 +2,6 @@ require "danbooru_image_resizer/danbooru_image_resizer"
|
|||||||
require "tmpdir"
|
require "tmpdir"
|
||||||
|
|
||||||
class PendingPost < ActiveRecord::Base
|
class PendingPost < ActiveRecord::Base
|
||||||
class Error < Exception ; end
|
|
||||||
|
|
||||||
attr_accessor :file, :image_width, :image_height, :file_ext, :md5, :file_size
|
attr_accessor :file, :image_width, :image_height, :file_ext, :md5, :file_size
|
||||||
belongs_to :uploader, :class_name => "User"
|
belongs_to :uploader, :class_name => "User"
|
||||||
before_save :convert_cgi_file
|
before_save :convert_cgi_file
|
||||||
@@ -26,24 +24,56 @@ class PendingPost < ActiveRecord::Base
|
|||||||
update_attribute(:status, "error: " + post.errors.full_messages.join(", "))
|
update_attribute(:status, "error: " + post.errors.full_messages.join(", "))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def convert_to_post
|
||||||
|
returning Post.new do |p|
|
||||||
|
p.tag_string = tag_string
|
||||||
|
p.md5 = md5
|
||||||
|
p.file_ext = file_ext
|
||||||
|
p.image_width = image_width
|
||||||
|
p.image_height = image_height
|
||||||
|
p.uploader_id = uploader_id
|
||||||
|
p.uploader_ip_addr = uploader_ip_addr
|
||||||
|
p.updater_id = uploader_id
|
||||||
|
p.updater_ip_addr = uploader_ip_addr
|
||||||
|
p.rating = rating
|
||||||
|
p.source = source
|
||||||
|
p.file_size = file_size
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def move_file
|
||||||
|
FileUtils.mv(file_path, md5_file_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
def calculate_file_size(source_path)
|
||||||
|
self.file_size = File.size(source_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Calculates the MD5 based on whatever is in temp_file_path
|
||||||
|
def calculate_hash(source_path)
|
||||||
|
self.md5 = Digest::MD5.file(source_path).hexdigest
|
||||||
|
end
|
||||||
|
|
||||||
|
class Error < Exception ; end
|
||||||
|
|
||||||
# private
|
|
||||||
module ResizerMethods
|
module ResizerMethods
|
||||||
def generate_resizes(source_path)
|
def generate_resizes(source_path)
|
||||||
generate_resize_for(Danbooru.config.small_image_width, source_path)
|
generate_resize_for(Danbooru.config.small_image_width, Danbooru.config.small_image_width, source_path)
|
||||||
generate_resize_for(Danbooru.config.medium_image_width, source_path)
|
generate_resize_for(Danbooru.config.medium_image_width, nil, source_path)
|
||||||
generate_resize_for(Danbooru.config.large_image_width, source_path)
|
generate_resize_for(Danbooru.config.large_image_width, nil, source_path)
|
||||||
end
|
end
|
||||||
|
|
||||||
def generate_resize_for(width, source_path)
|
def generate_resize_for(width, height, source_path)
|
||||||
return if width.nil?
|
return if width.nil?
|
||||||
return unless image_width > width
|
return unless image_width > width
|
||||||
|
return unless height.nil? || image_height > height
|
||||||
|
|
||||||
unless File.exists?(source_path)
|
unless File.exists?(source_path)
|
||||||
raise Error.new("file not found")
|
raise Error.new("file not found")
|
||||||
end
|
end
|
||||||
|
|
||||||
size = Danbooru.reduce_to({:width => image_width, :height => image_height}, {:width => width})
|
size = Danbooru.reduce_to({:width => image_width, :height => image_height}, {:width => width, :height => height})
|
||||||
|
|
||||||
# If we're not reducing the resolution, only reencode if the source image larger than
|
# If we're not reducing the resolution, only reencode if the source image larger than
|
||||||
# 200 kilobytes.
|
# 200 kilobytes.
|
||||||
@@ -177,33 +207,4 @@ class PendingPost < ActiveRecord::Base
|
|||||||
include DownloaderMethods
|
include DownloaderMethods
|
||||||
include FilePathMethods
|
include FilePathMethods
|
||||||
include CgiFileMethods
|
include CgiFileMethods
|
||||||
|
|
||||||
# private
|
|
||||||
def convert_to_post
|
|
||||||
returning Post.new do |p|
|
|
||||||
p.tag_string = tag_string
|
|
||||||
p.md5 = md5
|
|
||||||
p.file_ext = file_ext
|
|
||||||
p.image_width = image_width
|
|
||||||
p.image_height = image_height
|
|
||||||
p.uploader_id = uploader_id
|
|
||||||
p.uploader_ip_addr = uploader_ip_addr
|
|
||||||
p.rating = rating
|
|
||||||
p.source = source
|
|
||||||
p.file_size = file_size
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def move_file
|
|
||||||
FileUtils.mv(file_path, md5_file_path)
|
|
||||||
end
|
|
||||||
|
|
||||||
def calculate_file_size(source_path)
|
|
||||||
self.file_size = File.size(source_path)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Calculates the MD5 based on whatever is in temp_file_path
|
|
||||||
def calculate_hash(source_path)
|
|
||||||
self.md5 = Digest::MD5.file(source_path).hexdigest
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
@@ -1,6 +1,475 @@
|
|||||||
class Post < ActiveRecord::Base
|
class Post < ActiveRecord::Base
|
||||||
def file_path
|
attr_accessor :updater_id, :updater_ip_addr, :old_tag_string
|
||||||
prefix = Rails.env == "test" ? "test." : ""
|
belongs_to :updater, :class_name => "User"
|
||||||
"#{Rails.root}/public/data/original/#{prefix}#{md5}.#{file_ext}"
|
belongs_to :uploader, :class_name => "User"
|
||||||
|
has_one :unapproval
|
||||||
|
after_destroy :delete_files
|
||||||
|
after_save :create_version
|
||||||
|
before_save :merge_old_tags
|
||||||
|
before_save :normalize_tags
|
||||||
|
has_many :versions, :class_name => "PostVersion"
|
||||||
|
|
||||||
|
module FileMethods
|
||||||
|
def delete_files
|
||||||
|
FileUtils.rm_f(file_path)
|
||||||
|
FileUtils.rm_f(medium_file_path)
|
||||||
|
FileUtils.rm_f(large_file_path)
|
||||||
|
FileUtils.rm_f(thumb_file_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
def file_path_prefix
|
||||||
|
Rails.env == "test" ? "test." : ""
|
||||||
|
end
|
||||||
|
|
||||||
|
def file_path
|
||||||
|
"#{Rails.root}/public/data/original/#{file_path_prefix}#{md5}.#{file_ext}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def medium_file_path
|
||||||
|
"#{Rails.root}/public/data/medium/#{file_path_prefix}#{md5}.jpg"
|
||||||
|
end
|
||||||
|
|
||||||
|
def large_file_path
|
||||||
|
"#{Rails.root}/public/data/large/#{file_path_prefix}#{md5}.jpg"
|
||||||
|
end
|
||||||
|
|
||||||
|
def thumb_file_path
|
||||||
|
"#{Rails.root}/public/data/thumb/#{file_path_prefix}#{md5}.jpg"
|
||||||
|
end
|
||||||
|
|
||||||
|
def file_url
|
||||||
|
"/data/original/#{file_path_prefix}#{md5}.#{file_ext}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def medium_file_url
|
||||||
|
"/data/medium/#{file_path_prefix}#{md5}.jpg"
|
||||||
|
end
|
||||||
|
|
||||||
|
def large_file_url
|
||||||
|
"/data/large/#{file_path_prefix}#{md5}.jpg"
|
||||||
|
end
|
||||||
|
|
||||||
|
def thumb_file_url
|
||||||
|
"/data/thumb/#{file_path_prefix}#{md5}.jpg"
|
||||||
|
end
|
||||||
|
|
||||||
|
def file_url_for(user)
|
||||||
|
case user.default_image_size
|
||||||
|
when "medium"
|
||||||
|
medium_file_url
|
||||||
|
|
||||||
|
when "large"
|
||||||
|
large_file_url
|
||||||
|
|
||||||
|
else
|
||||||
|
file_url
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
module ImageMethods
|
||||||
|
def has_medium?
|
||||||
|
image_width > Danbooru.config.medium_image_width
|
||||||
|
end
|
||||||
|
|
||||||
|
def has_large?
|
||||||
|
image_width > Danbooru.config.large_image_width
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module ModerationMethods
|
||||||
|
def unapprove!(reason, current_user, current_ip_addr)
|
||||||
|
raise Unapproval::Error.new("You can't unapprove a post more than once") if is_flagged?
|
||||||
|
|
||||||
|
unapproval = create_unapproval(
|
||||||
|
:unapprover_id => current_user.id,
|
||||||
|
:unapprover_ip_addr => current_ip_addr,
|
||||||
|
:reason => reason
|
||||||
|
)
|
||||||
|
|
||||||
|
if unapproval.errors.any?
|
||||||
|
raise Unapproval::Error.new(unapproval.errors.full_messages.join("; "))
|
||||||
|
end
|
||||||
|
|
||||||
|
update_attribute(:is_flagged, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete!
|
||||||
|
update_attribute(:is_deleted, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
def approve!
|
||||||
|
update_attributes(:is_deleted => false, :is_pending => false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module PresenterMethods
|
||||||
|
def pretty_rating
|
||||||
|
case rating
|
||||||
|
when "q"
|
||||||
|
"Questionable"
|
||||||
|
|
||||||
|
when "e"
|
||||||
|
"Explicit"
|
||||||
|
|
||||||
|
when "s"
|
||||||
|
"Safe"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module VersionMethods
|
||||||
|
def create_version
|
||||||
|
version = versions.create(
|
||||||
|
:source => source,
|
||||||
|
:rating => rating,
|
||||||
|
:tag_string => tag_string,
|
||||||
|
:updater_id => updater_id,
|
||||||
|
:updater_ip_addr => updater_ip_addr
|
||||||
|
)
|
||||||
|
|
||||||
|
raise PostVersion::Error.new(version.errors.full_messages.join("; ")) if version.errors.any?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module TagMethods
|
||||||
|
def tag_array(reload = false)
|
||||||
|
if @tag_array.nil? || reload
|
||||||
|
@tag_array = Tag.scan_tags(tag_string)
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag_array
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_tag_counts
|
||||||
|
self.tag_count = 0
|
||||||
|
self.tag_count_general = 0
|
||||||
|
self.tag_count_artist = 0
|
||||||
|
self.tag_count_copyright = 0
|
||||||
|
self.tag_count_character = 0
|
||||||
|
|
||||||
|
categories = Tag.categories_for(tag_array)
|
||||||
|
categories.each_value do |category|
|
||||||
|
self.tag_count += 1
|
||||||
|
|
||||||
|
case category
|
||||||
|
when Tag.categories.general
|
||||||
|
self.tag_count_general += 1
|
||||||
|
|
||||||
|
when Tag.categories.artist
|
||||||
|
self.tag_count_artist += 1
|
||||||
|
|
||||||
|
when Tag.categories.copyright
|
||||||
|
self.tag_count_copyright += 1
|
||||||
|
|
||||||
|
when Tag.categories.character
|
||||||
|
self.tag_count_character += 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def merge_old_tags
|
||||||
|
if old_tag_string
|
||||||
|
# If someone else committed changes to this post before we did,
|
||||||
|
# then try to merge the tag changes together.
|
||||||
|
db_tags = Tag.scan_tags(tag_string_was)
|
||||||
|
new_tags = tag_array()
|
||||||
|
old_tags = Tag.scan_tags(old_tag_string)
|
||||||
|
self.tag_string = (db_tags + (new_tags - old_tags) - (old_tags - new_tags)).uniq.join(" ")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def normalize_tags
|
||||||
|
normalized_tags = Tag.scan_tags(tag_string)
|
||||||
|
# normalized_tags = TagAlias.to_aliased(normalized_tags)
|
||||||
|
# normalized_tags = TagImplication.with_implications(normalized_tags)
|
||||||
|
normalized_tags = parse_metatags(normalized_tags)
|
||||||
|
self.tag_string = normalized_tags.uniq.join(" ")
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_metatags(tags)
|
||||||
|
tags.map do |tag|
|
||||||
|
if tag =~ /^(?:pool|rating|fav|user|uploader):(.+)/
|
||||||
|
case $1
|
||||||
|
when "pool"
|
||||||
|
parse_pool_tag($2)
|
||||||
|
|
||||||
|
when "rating"
|
||||||
|
parse_rating_tag($2)
|
||||||
|
|
||||||
|
when "fav"
|
||||||
|
parse_fav_tag($2)
|
||||||
|
|
||||||
|
when "uploader"
|
||||||
|
# ignore
|
||||||
|
|
||||||
|
when "user"
|
||||||
|
# ignore
|
||||||
|
end
|
||||||
|
|
||||||
|
nil
|
||||||
|
else
|
||||||
|
tag
|
||||||
|
end
|
||||||
|
end.compact
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_pool_tag(text)
|
||||||
|
case text
|
||||||
|
when "new"
|
||||||
|
pool = Pool.create_anonymous
|
||||||
|
pool.posts << self
|
||||||
|
|
||||||
|
when "recent"
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
when /^\d+$/
|
||||||
|
pool = Pool.find_by_id(text.to_i)
|
||||||
|
pool.posts << self if pool
|
||||||
|
|
||||||
|
else
|
||||||
|
pool = Pool.find_by_name(text)
|
||||||
|
pool.posts << self if pool
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_rating_tag(rating)
|
||||||
|
case rating
|
||||||
|
when /q/
|
||||||
|
self.rating = "q"
|
||||||
|
|
||||||
|
when /e/
|
||||||
|
self.rating = "e"
|
||||||
|
|
||||||
|
when /s/
|
||||||
|
self.rating = "s"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_fav_tag(text)
|
||||||
|
case text
|
||||||
|
when "add", "new"
|
||||||
|
add_favorite(updater_id)
|
||||||
|
|
||||||
|
when "remove", "rem", "del"
|
||||||
|
remove_favorite(updater_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module FavoriteMethods
|
||||||
|
def add_favorite(user_id)
|
||||||
|
self.fav_string += " fav:#{user_id}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove_favorite(user_id)
|
||||||
|
self.fav_string.gsub!(/user:#{user_id}\b\s*/, " ")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module SearchMethods
|
||||||
|
class SearchError < Exception ; end
|
||||||
|
|
||||||
|
def add_range_relation(arr, field, arel)
|
||||||
|
case arr[0]
|
||||||
|
when :eq
|
||||||
|
arel.where(["#{field} = ?", arr[1]])
|
||||||
|
|
||||||
|
when :gt
|
||||||
|
arel.where(["#{field} > ?"], arr[1])
|
||||||
|
|
||||||
|
when :gte
|
||||||
|
arel.where(["#{field} >= ?", arr[1]])
|
||||||
|
|
||||||
|
when :lt
|
||||||
|
arel.where(["#{field} < ?", arr[1]])
|
||||||
|
|
||||||
|
when :lte
|
||||||
|
arel.where(["#{field} <= ?", arr[1]])
|
||||||
|
|
||||||
|
when :between
|
||||||
|
arel.where(["#{field} BETWEEN ? AND ?", arr[1], arr[2]])
|
||||||
|
|
||||||
|
else
|
||||||
|
# do nothing
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def escape_string_for_tsquery(array)
|
||||||
|
array.map do |token|
|
||||||
|
escaped_token = token.gsub(/\\|'/, '\0\0\0\0').gsub("?", "\\\\77").gsub("%", "\\\\37")
|
||||||
|
"''" + escaped_token + "''"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_relation(q, options = {})
|
||||||
|
unless q.is_a?(Hash)
|
||||||
|
q = Tag.parse_query(q)
|
||||||
|
end
|
||||||
|
|
||||||
|
relation = where()
|
||||||
|
|
||||||
|
add_range_relation(q[:post_id], "posts.id", relation)
|
||||||
|
add_range_relation(q[:mpixels], "posts.width * posts.height / 1000000.0", relation)
|
||||||
|
add_range_relation(q[:width], "posts.image_width", relation)
|
||||||
|
add_range_relation(q[:height], "posts.image_height", relation)
|
||||||
|
add_range_relation(q[:score], "posts.score", relation)
|
||||||
|
add_range_relation(q[:filesize], "posts.file_size", relation)
|
||||||
|
add_range_relation(q[:date], "posts.created_at::date", relation)
|
||||||
|
add_range_relation(q[:general_tag_count], "posts.tag_count_general", relation)
|
||||||
|
add_range_relation(q[:artist_tag_count], "posts.tag_count_artist", relation)
|
||||||
|
add_range_relation(q[:copyright_tag_count], "posts.tag_count_copyright", relation)
|
||||||
|
add_range_relation(q[:character_tag_count], "posts.tag_count_character", relation)
|
||||||
|
add_range_relation(q[:tag_count], "posts.tag_count", relation)
|
||||||
|
|
||||||
|
if options[:before_id]
|
||||||
|
relation.where(["posts.id < ?", options[:before_id]])
|
||||||
|
end
|
||||||
|
|
||||||
|
if q[:md5].is_a?(String)
|
||||||
|
relation.where(["posts.md5 IN (?)", q[:md5].split(/,/)])
|
||||||
|
end
|
||||||
|
|
||||||
|
if q[:status] == "deleted"
|
||||||
|
relation.where("posts.is_deleted = TRUE")
|
||||||
|
elsif q[:status] == "pending"
|
||||||
|
relation.where("posts.is_pending = TRUE")
|
||||||
|
elsif q[:status] == "flagged"
|
||||||
|
relation.where("posts.is_flagged = TRUE")
|
||||||
|
else
|
||||||
|
relation.where("posts.is_deleted = FALSE")
|
||||||
|
end
|
||||||
|
|
||||||
|
if q[:source].is_a?(String)
|
||||||
|
relation.where(["posts.source LIKE ? ESCAPE E'\\\\", q[:source]])
|
||||||
|
end
|
||||||
|
|
||||||
|
if q[:subscriptions].is_a?(String)
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
q[:subscriptions] =~ /^(.+?):(.+)$/
|
||||||
|
username = $1 || q[:subscriptions]
|
||||||
|
subscription_name = $2
|
||||||
|
|
||||||
|
user = User.find_by_name(username)
|
||||||
|
|
||||||
|
if user
|
||||||
|
post_ids = TagSubscription.find_post_ids(user.id, subscription_name)
|
||||||
|
relation.where(["posts.id IN (?)", post_ids])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
tag_query_sql = []
|
||||||
|
|
||||||
|
if q[:include].any?
|
||||||
|
tag_query_sql << "(" + escape_string_for_tsquery(q[:include]).join(" | ") + ")"
|
||||||
|
end
|
||||||
|
|
||||||
|
if q[:related].any?
|
||||||
|
raise SearchError.new("You cannot search for more than #{Danbooru.config.tag_query_limit} tags at a time") if q[:related].size > Danbooru.config.tag_query_limit
|
||||||
|
tag_query_sql << "(" + escape_string_for_tsquery(q[:related]).join(" & ") + ")"
|
||||||
|
end
|
||||||
|
|
||||||
|
if q[:exclude].any?
|
||||||
|
raise SearchError.new("You cannot search for more than #{Danbooru.config.tag_query_limit} tags at a time") if q[:exclude].size > Danbooru.config.tag_query_limit
|
||||||
|
|
||||||
|
if q[:related].any? || q[:include].any?
|
||||||
|
tag_query_sql << "!(" + escape_string_for_tsquery(q[:exclude]).join(" | ") + ")"
|
||||||
|
else
|
||||||
|
raise SearchError.new("You cannot search for only excluded tags")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if tag_query_sql.any?
|
||||||
|
relation.where("posts.tag_index @@ to_tsquery('danbooru', E'" + tag_query_sql.join(" & ") + "')")
|
||||||
|
end
|
||||||
|
|
||||||
|
if q[:rating] == "q"
|
||||||
|
relation.where("posts.rating = 'q'")
|
||||||
|
elsif q[:rating] == "s"
|
||||||
|
relation.where("posts.rating = 's'")
|
||||||
|
elsif q[:rating] == "e"
|
||||||
|
relation.where("posts.rating = 'e'")
|
||||||
|
end
|
||||||
|
|
||||||
|
if q[:rating_negated] == "q"
|
||||||
|
relation.where("posts.rating <> 'q'")
|
||||||
|
elsif q[:rating_negated] == "s"
|
||||||
|
relation.where("posts.rating <> 's'")
|
||||||
|
elsif q[:rating_negated] == "e"
|
||||||
|
relation.where("posts.rating <> 'e'")
|
||||||
|
end
|
||||||
|
|
||||||
|
case q[:order]
|
||||||
|
when "id", "id_asc"
|
||||||
|
relation.order("posts.id")
|
||||||
|
|
||||||
|
when "id_desc"
|
||||||
|
relation.order("posts.id DESC")
|
||||||
|
|
||||||
|
when "score", "score_desc"
|
||||||
|
relation.order("posts.score DESC, posts.id DESC")
|
||||||
|
|
||||||
|
when "score_asc"
|
||||||
|
relation.order("posts.score, posts.id DESC")
|
||||||
|
|
||||||
|
when "mpixels", "mpixels_desc"
|
||||||
|
# Use "w*h/1000000", even though "w*h" would give the same result, so this can use
|
||||||
|
# the posts_mpixels index.
|
||||||
|
relation.order("posts.image_width * posts.image_height / 1000000.0 DESC, posts.id DESC")
|
||||||
|
|
||||||
|
when "mpixels_asc"
|
||||||
|
relation.order("posts.image_width * posts.image_height / 1000000.0, posts.id DESC")
|
||||||
|
|
||||||
|
when "portrait"
|
||||||
|
relation.order("1.0 * image_width / GREATEST(1, image_height), posts.id DESC")
|
||||||
|
|
||||||
|
when "landscape"
|
||||||
|
relation.order("1.0 * image_width / GREATEST(1, image_height) DESC, p.id DESC")
|
||||||
|
|
||||||
|
when "filesize", "filesize_desc"
|
||||||
|
relation.order("posts.file_size DESC")
|
||||||
|
|
||||||
|
when "filesize_asc"
|
||||||
|
relation.order("posts.file_size")
|
||||||
|
|
||||||
|
else
|
||||||
|
relation.order("posts.id DESC")
|
||||||
|
end
|
||||||
|
|
||||||
|
if options[:limit]
|
||||||
|
relation.limit(options[:limit])
|
||||||
|
end
|
||||||
|
|
||||||
|
if options[:offset]
|
||||||
|
relation.offset(options[:offset])
|
||||||
|
end
|
||||||
|
|
||||||
|
relation
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_by_tags(tags, options)
|
||||||
|
hash = Tag.parse_query(tags)
|
||||||
|
build_relation(hash, options)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module UploaderMethods
|
||||||
|
def uploader_id=(user_id)
|
||||||
|
self.uploader_string = "user:#{user_id}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def uploader_id
|
||||||
|
uploader_string[5, 100].to_i
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
include FileMethods
|
||||||
|
include ImageMethods
|
||||||
|
include ModerationMethods
|
||||||
|
include PresenterMethods
|
||||||
|
include VersionMethods
|
||||||
|
include TagMethods
|
||||||
|
include FavoriteMethods
|
||||||
|
include UploaderMethods
|
||||||
end
|
end
|
||||||
|
|||||||
5
app/models/post_version.rb
Normal file
5
app/models/post_version.rb
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
class PostVersion < ActiveRecord::Base
|
||||||
|
class Error < Exception ; end
|
||||||
|
|
||||||
|
validates_presence_of :updater_id, :updater_ip_addr
|
||||||
|
end
|
||||||
@@ -1,4 +1,7 @@
|
|||||||
class Tag < ActiveRecord::Base
|
class Tag < ActiveRecord::Base
|
||||||
|
attr_accessible :category
|
||||||
|
after_save :update_category_cache
|
||||||
|
|
||||||
class CategoryMapping
|
class CategoryMapping
|
||||||
Danbooru.config.reverse_tag_category_mapping.each do |value, category|
|
Danbooru.config.reverse_tag_category_mapping.each do |value, category|
|
||||||
define_method(category.downcase) do
|
define_method(category.downcase) do
|
||||||
@@ -14,212 +17,256 @@ class Tag < ActiveRecord::Base
|
|||||||
Danbooru.config.tag_category_mapping[string.downcase] || 0
|
Danbooru.config.tag_category_mapping[string.downcase] || 0
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
attr_accessible :category
|
|
||||||
|
|
||||||
after_save {|rec| Cache.put("tag_type:#{cache_safe_name}", rec.category_name)}
|
|
||||||
|
|
||||||
|
|
||||||
### Category Methods ###
|
|
||||||
def self.categories
|
|
||||||
@category_mapping ||= CategoryMapping.new
|
|
||||||
end
|
|
||||||
|
|
||||||
def category_name
|
|
||||||
Danbooru.config.reverse_tag_category_mapping[category]
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
### Statistics Methods ###
|
|
||||||
def self.trending
|
|
||||||
raise NotImplementedError
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
### Name Methods ###
|
|
||||||
def self.normalize_name(name)
|
|
||||||
name.downcase.tr(" ", "_").gsub(/\A[-~*]+/, "")
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.find_or_create_by_name(name, options = {})
|
|
||||||
name = normalize_name(name)
|
|
||||||
category = categories.general
|
|
||||||
|
|
||||||
if name =~ /\A(#{categories.regexp}):(.+)\Z/
|
module ViewCountMethods
|
||||||
category = categories.value_for($1)
|
def increment_view_count(name)
|
||||||
name = $2
|
Cache.incr("tvc:#{Cache.sanitize(name)}")
|
||||||
end
|
end
|
||||||
|
end
|
||||||
tag = find_by_name(name)
|
|
||||||
|
module CategoryMethods
|
||||||
if tag
|
module ClassMethods
|
||||||
if category > 0 && !(options[:user] && !options[:user].is_privileged? && tag.post_count > 10)
|
def categories
|
||||||
tag.update_attribute(:category, category)
|
@category_mapping ||= CategoryMapping.new
|
||||||
end
|
end
|
||||||
|
|
||||||
tag
|
def category_for(tag_name)
|
||||||
else
|
Cache.get("tc:#{Cache.sanitize(tag_name)}") do
|
||||||
returning Tag.new do |tag|
|
select_value_sql("SELECT category FROM tags WHERE name = ?", tag_name).to_i
|
||||||
tag.name = name
|
|
||||||
tag.category = category
|
|
||||||
tag.save
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def cache_safe_name
|
|
||||||
name.gsub(/[^a-zA-Z0-9_-]/, "_")
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
### Update methods ###
|
|
||||||
def self.mass_edit(start_tags, result_tags, updater_id, updater_ip_addr)
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
Post.find_by_tags(start_tags).each do |p|
|
|
||||||
start = TagAlias.to_aliased(scan_tags(start_tags))
|
|
||||||
result = TagAlias.to_aliased(scan_tags(result_tags))
|
|
||||||
tags = (p.cached_tags.scan(/\S+/) - start + result).join(" ")
|
|
||||||
p.update_attributes(:updater_user_id => updater_id, :updater_ip_addr => updater_ip_addr, :tags => tags)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
### Parse Methods ###
|
|
||||||
def self.scan_query(query)
|
|
||||||
query.to_s.downcase.scan(/\S+/).uniq
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.scan_tags(tags)
|
|
||||||
tags.to_s.downcase.gsub(/[,;*]/, "_").scan(/\S+/).uniq
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.parse_cast(object, type)
|
|
||||||
case type
|
|
||||||
when :integer
|
|
||||||
object.to_i
|
|
||||||
|
|
||||||
when :float
|
|
||||||
object.to_f
|
|
||||||
|
|
||||||
when :date
|
|
||||||
begin
|
|
||||||
object.to_date
|
|
||||||
rescue Exception
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
when :filesize
|
|
||||||
object =~ /^(\d+(?:\.\d*)?|\d*\.\d+)([kKmM]?)[bB]?$/
|
|
||||||
|
|
||||||
size = $1.to_f
|
|
||||||
unit = $2
|
|
||||||
|
|
||||||
conversion_factor = case unit
|
|
||||||
when /m/i
|
|
||||||
1024 * 1024
|
|
||||||
when /k/i
|
|
||||||
1024
|
|
||||||
else
|
|
||||||
1
|
|
||||||
end
|
|
||||||
|
|
||||||
(size * conversion_factor).to_i
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.parse_helper(range, type = :integer)
|
|
||||||
# "1", "0.5", "5.", ".5":
|
|
||||||
# (-?(\d+(\.\d*)?|\d*\.\d+))
|
|
||||||
case range
|
|
||||||
when /^(.+?)\.\.(.+)/
|
|
||||||
return [:between, parse_cast($1, type), parse_cast($2, type)]
|
|
||||||
|
|
||||||
when /^<=(.+)/, /^\.\.(.+)/
|
|
||||||
return [:lte, parse_cast($1, type)]
|
|
||||||
|
|
||||||
when /^<(.+)/
|
|
||||||
return [:lt, parse_cast($1, type)]
|
|
||||||
|
|
||||||
when /^>=(.+)/, /^(.+)\.\.$/
|
|
||||||
return [:gte, parse_cast($1, type)]
|
|
||||||
|
|
||||||
when /^>(.+)/
|
|
||||||
return [:gt, parse_cast($1, type)]
|
|
||||||
|
|
||||||
else
|
|
||||||
return [:eq, parse_cast(range, type)]
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.parse_query(query, options = {})
|
|
||||||
q = Hash.new {|h, k| h[k] = []}
|
|
||||||
|
|
||||||
scan_query(query).each do |token|
|
|
||||||
if token =~ /^(sub|md5|-rating|rating|width|height|mpixels|score|filesize|source|id|date|order|change|status|tagcount|gentagcount|arttagcount|chartagcount|copytagcount):(.+)$/
|
|
||||||
if $1 == "sub"
|
|
||||||
q[:subscriptions] = $2
|
|
||||||
elsif $1 == "md5"
|
|
||||||
q[:md5] = $2
|
|
||||||
elsif $1 == "-rating"
|
|
||||||
q[:rating_negated] = $2
|
|
||||||
elsif $1 == "rating"
|
|
||||||
q[:rating] = $2
|
|
||||||
elsif $1 == "id"
|
|
||||||
q[:post_id] = parse_helper($2)
|
|
||||||
elsif $1 == "width"
|
|
||||||
q[:width] = parse_helper($2)
|
|
||||||
elsif $1 == "height"
|
|
||||||
q[:height] = parse_helper($2)
|
|
||||||
elsif $1 == "mpixels"
|
|
||||||
q[:mpixels] = parse_helper($2, :float)
|
|
||||||
elsif $1 == "score"
|
|
||||||
q[:score] = parse_helper($2)
|
|
||||||
elsif $1 == "filesize"
|
|
||||||
q[:filesize] = parse_helper($2, :filesize)
|
|
||||||
elsif $1 == "source"
|
|
||||||
q[:source] = $2.to_escaped_for_sql_like + "%"
|
|
||||||
elsif $1 == "date"
|
|
||||||
q[:date] = parse_helper($2, :date)
|
|
||||||
elsif $1 == "tagcount"
|
|
||||||
q[:tag_count] = parse_helper($2)
|
|
||||||
elsif $1 == "gentagcount"
|
|
||||||
q[:general_tag_count] = parse_helper($2)
|
|
||||||
elsif $1 == "arttagcount"
|
|
||||||
q[:artist_tag_count] = parse_helper($2)
|
|
||||||
elsif $1 == "chartagcount"
|
|
||||||
q[:character_tag_count] = parse_helper($2)
|
|
||||||
elsif $1 == "copytagcount"
|
|
||||||
q[:copyright_tag_count] = parse_helper($2)
|
|
||||||
elsif $1 == "order"
|
|
||||||
q[:order] = $2
|
|
||||||
elsif $1 == "change"
|
|
||||||
q[:change] = parse_helper($2)
|
|
||||||
elsif $1 == "status"
|
|
||||||
q[:status] = $2
|
|
||||||
end
|
end
|
||||||
elsif token[0] == "-" && token.size > 1
|
end
|
||||||
q[:exclude] << token[1..-1]
|
|
||||||
elsif token[0] == "~" && token.size > 1
|
def categories_for(tag_names)
|
||||||
q[:include] << token[1..-1]
|
key_hash = tag_names.inject({}) do |hash, x|
|
||||||
elsif token.include?("*")
|
hash[x] = "tc:#{Cache.sanitize(x)}"
|
||||||
matches = where(["name LIKE ? ESCAPE E'\\\\'", token.to_escaped_for_sql_like]).all(:select => "name", :limit => 25, :order => "post_count DESC").map(&:name)
|
hash
|
||||||
matches = ["~no_matches~"] if matches.empty?
|
end
|
||||||
q[:include] += matches
|
categories_hash = MEMCACHE.get_multi(key_hash.values)
|
||||||
else
|
returning({}) do |result_hash|
|
||||||
q[:related] << token
|
key_hash.each do |tag_name, hash_key|
|
||||||
|
if categories_hash.has_key?(hash_key)
|
||||||
|
result_hash[tag_name] = categories_hash[hash_key]
|
||||||
|
else
|
||||||
|
result_hash[tag_name] = category_for(tag_name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.included(m)
|
||||||
|
m.extend(ClassMethods)
|
||||||
|
end
|
||||||
|
|
||||||
|
def category_name
|
||||||
|
Danbooru.config.reverse_tag_category_mapping[category]
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_category_cache
|
||||||
|
Cache.put("tc:#{Cache.sanitize(name)}", category)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module StatisticsMethods
|
||||||
|
def trending
|
||||||
|
raise NotImplementedError
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module NameMethods
|
||||||
|
module ClassMethods
|
||||||
|
def normalize_name(name)
|
||||||
|
name.downcase.tr(" ", "_").gsub(/\A[-~*]+/, "")
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_or_create_by_name(name, options = {})
|
||||||
|
name = normalize_name(name)
|
||||||
|
category = categories.general
|
||||||
|
|
||||||
|
if name =~ /\A(#{categories.regexp}):(.+)\Z/
|
||||||
|
category = categories.value_for($1)
|
||||||
|
name = $2
|
||||||
|
end
|
||||||
|
|
||||||
|
tag = find_by_name(name)
|
||||||
|
|
||||||
|
if tag
|
||||||
|
if category > 0 && !(options[:user] && !options[:user].is_privileged? && tag.post_count > 10)
|
||||||
|
tag.update_attribute(:category, category)
|
||||||
|
end
|
||||||
|
|
||||||
|
tag
|
||||||
|
else
|
||||||
|
returning Tag.new do |tag|
|
||||||
|
tag.name = name
|
||||||
|
tag.category = category
|
||||||
|
tag.save
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.included(m)
|
||||||
|
m.extend(ClassMethods)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module UpdateMethods
|
||||||
|
def mass_edit(start_tags, result_tags, updater_id, updater_ip_addr)
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
Post.find_by_tags(start_tags).each do |p|
|
||||||
|
start = TagAlias.to_aliased(scan_tags(start_tags))
|
||||||
|
result = TagAlias.to_aliased(scan_tags(result_tags))
|
||||||
|
tags = (p.cached_tags.scan(/\S+/) - start + result).join(" ")
|
||||||
|
p.update_attributes(:updater_user_id => updater_id, :updater_ip_addr => updater_ip_addr, :tags => tags)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module ParseMethods
|
||||||
|
def scan_query(query)
|
||||||
|
query.to_s.downcase.scan(/\S+/).uniq
|
||||||
|
end
|
||||||
|
|
||||||
|
def scan_tags(tags)
|
||||||
|
tags.to_s.downcase.gsub(/[,;*]/, "_").scan(/\S+/).uniq
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_cast(object, type)
|
||||||
|
case type
|
||||||
|
when :integer
|
||||||
|
object.to_i
|
||||||
|
|
||||||
|
when :float
|
||||||
|
object.to_f
|
||||||
|
|
||||||
|
when :date
|
||||||
|
begin
|
||||||
|
object.to_date
|
||||||
|
rescue Exception
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
when :filesize
|
||||||
|
object =~ /\A(\d+(?:\.\d*)?|\d*\.\d+)([kKmM]?)[bB]?\Z/
|
||||||
|
|
||||||
|
size = $1.to_f
|
||||||
|
unit = $2
|
||||||
|
|
||||||
|
conversion_factor = case unit
|
||||||
|
when /m/i
|
||||||
|
1024 * 1024
|
||||||
|
when /k/i
|
||||||
|
1024
|
||||||
|
else
|
||||||
|
1
|
||||||
|
end
|
||||||
|
|
||||||
|
(size * conversion_factor).to_i
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
normalize_tags_in_query(q)
|
def parse_helper(range, type = :integer)
|
||||||
|
# "1", "0.5", "5.", ".5":
|
||||||
|
# (-?(\d+(\.\d*)?|\d*\.\d+))
|
||||||
|
case range
|
||||||
|
when /\A(.+?)\.\.(.+)/
|
||||||
|
return [:between, parse_cast($1, type), parse_cast($2, type)]
|
||||||
|
|
||||||
return q
|
when /\A<=(.+)/, /\A\.\.(.+)/
|
||||||
|
return [:lte, parse_cast($1, type)]
|
||||||
|
|
||||||
|
when /\A<(.+)/
|
||||||
|
return [:lt, parse_cast($1, type)]
|
||||||
|
|
||||||
|
when /\A>=(.+)/, /\A(.+)\.\.\Z/
|
||||||
|
return [:gte, parse_cast($1, type)]
|
||||||
|
|
||||||
|
when /\A>(.+)/
|
||||||
|
return [:gt, parse_cast($1, type)]
|
||||||
|
|
||||||
|
else
|
||||||
|
return [:eq, parse_cast(range, type)]
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse_query(query, options = {})
|
||||||
|
q = Hash.new {|h, k| h[k] = []}
|
||||||
|
|
||||||
|
scan_query(query).each do |token|
|
||||||
|
if token =~ /\A(sub|md5|-rating|rating|width|height|mpixels|score|filesize|source|id|date|order|change|status|tagcount|gentagcount|arttagcount|chartagcount|copytagcount):(.+)\Z/
|
||||||
|
if $1 == "sub"
|
||||||
|
q[:subscriptions] = $2
|
||||||
|
elsif $1 == "md5"
|
||||||
|
q[:md5] = $2
|
||||||
|
elsif $1 == "-rating"
|
||||||
|
q[:rating_negated] = $2
|
||||||
|
elsif $1 == "rating"
|
||||||
|
q[:rating] = $2
|
||||||
|
elsif $1 == "id"
|
||||||
|
q[:post_id] = parse_helper($2)
|
||||||
|
elsif $1 == "width"
|
||||||
|
q[:width] = parse_helper($2)
|
||||||
|
elsif $1 == "height"
|
||||||
|
q[:height] = parse_helper($2)
|
||||||
|
elsif $1 == "mpixels"
|
||||||
|
q[:mpixels] = parse_helper($2, :float)
|
||||||
|
elsif $1 == "score"
|
||||||
|
q[:score] = parse_helper($2)
|
||||||
|
elsif $1 == "filesize"
|
||||||
|
q[:filesize] = parse_helper($2, :filesize)
|
||||||
|
elsif $1 == "source"
|
||||||
|
q[:source] = $2.to_escaped_for_sql_like + "%"
|
||||||
|
elsif $1 == "date"
|
||||||
|
q[:date] = parse_helper($2, :date)
|
||||||
|
elsif $1 == "tagcount"
|
||||||
|
q[:tag_count] = parse_helper($2)
|
||||||
|
elsif $1 == "gentagcount"
|
||||||
|
q[:general_tag_count] = parse_helper($2)
|
||||||
|
elsif $1 == "arttagcount"
|
||||||
|
q[:artist_tag_count] = parse_helper($2)
|
||||||
|
elsif $1 == "chartagcount"
|
||||||
|
q[:character_tag_count] = parse_helper($2)
|
||||||
|
elsif $1 == "copytagcount"
|
||||||
|
q[:copyright_tag_count] = parse_helper($2)
|
||||||
|
elsif $1 == "order"
|
||||||
|
q[:order] = $2
|
||||||
|
elsif $1 == "change"
|
||||||
|
q[:change] = parse_helper($2)
|
||||||
|
elsif $1 == "status"
|
||||||
|
q[:status] = $2
|
||||||
|
end
|
||||||
|
elsif token[0] == "-" && token.size > 1
|
||||||
|
q[:exclude] << token[1..-1]
|
||||||
|
elsif token[0] == "~" && token.size > 1
|
||||||
|
q[:include] << token[1..-1]
|
||||||
|
elsif token.include?("*")
|
||||||
|
matches = where(["name LIKE ? ESCAPE E'\\\\'", token.to_escaped_for_sql_like]).all(:select => "name", :limit => 25, :order => "post_count DESC").map(&:name)
|
||||||
|
matches = ["~no_matches~"] if matches.empty?
|
||||||
|
q[:include] += matches
|
||||||
|
else
|
||||||
|
q[:related] << token
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
normalize_tags_in_query(q)
|
||||||
|
|
||||||
|
return q
|
||||||
|
end
|
||||||
|
|
||||||
|
def normalize_tags_in_query(query_hash)
|
||||||
|
query_hash[:exclude] = TagAlias.to_aliased(query_hash[:exclude], :strip_prefix => true) if query_hash.has_key?(:exclude)
|
||||||
|
query_hash[:include] = TagAlias.to_aliased(query_hash[:include], :strip_prefix => true) if query_hash.has_key?(:include)
|
||||||
|
query_hash[:related] = TagAlias.to_aliased(query_hash[:related]) if query_hash.has_key?(:related)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.normalize_tags_in_query(query_hash)
|
extend ViewCountMethods
|
||||||
query_hash[:exclude] = TagAlias.to_aliased(query_hash[:exclude], :strip_prefix => true) if query_hash.has_key?(:exclude)
|
include CategoryMethods
|
||||||
query_hash[:include] = TagAlias.to_aliased(query_hash[:include], :strip_prefix => true) if query_hash.has_key?(:include)
|
extend StatisticsMethods
|
||||||
query_hash[:related] = TagAlias.to_aliased(query_hash[:related]) if query_hash.has_key?(:related)
|
include NameMethods
|
||||||
end
|
extend UpdateMethods
|
||||||
|
extend ParseMethods
|
||||||
end
|
end
|
||||||
|
|||||||
6
app/models/unapproval.rb
Normal file
6
app/models/unapproval.rb
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
class Unapproval < ActiveRecord::Base
|
||||||
|
class Error < Exception ; end
|
||||||
|
|
||||||
|
belongs_to :unapprover, :class_name => "User"
|
||||||
|
validates_presence_of :reason, :unapprover_id, :unapprover_ip_addr
|
||||||
|
end
|
||||||
@@ -2,67 +2,77 @@ require 'digest/sha1'
|
|||||||
|
|
||||||
class User < ActiveRecord::Base
|
class User < ActiveRecord::Base
|
||||||
attr_accessor :password
|
attr_accessor :password
|
||||||
|
|
||||||
attr_accessible :password_hash, :email, :last_logged_in_at, :last_forum_read_at, :has_mail, :receive_email_notifications, :comment_threshold, :always_resize_images, :favorite_tags, :blacklisted_tags
|
attr_accessible :password_hash, :email, :last_logged_in_at, :last_forum_read_at, :has_mail, :receive_email_notifications, :comment_threshold, :always_resize_images, :favorite_tags, :blacklisted_tags
|
||||||
|
|
||||||
validates_length_of :name, :within => 2..20, :on => :create
|
validates_length_of :name, :within => 2..20, :on => :create
|
||||||
validates_format_of :name, :with => /\A[^\s;,]+\Z/, :on => :create, :message => "cannot have whitespace, commas, or semicolons"
|
validates_format_of :name, :with => /\A[^\s;,]+\Z/, :on => :create, :message => "cannot have whitespace, commas, or semicolons"
|
||||||
validates_uniqueness_of :name, :case_sensitive => false, :on => :create
|
validates_uniqueness_of :name, :case_sensitive => false, :on => :create
|
||||||
validates_uniqueness_of :email, :case_sensitive => false, :on => :create, :if => lambda {|rec| !rec.email.blank?}
|
validates_uniqueness_of :email, :case_sensitive => false, :on => :create, :if => lambda {|rec| !rec.email.blank?}
|
||||||
validates_length_of :password, :minimum => 5, :if => lambda {|rec| rec.password}
|
validates_length_of :password, :minimum => 5, :if => lambda {|rec| rec.password}
|
||||||
|
validates_inclusion_of :default_image_size, :in => %w(medium large original)
|
||||||
validates_confirmation_of :password
|
validates_confirmation_of :password
|
||||||
|
|
||||||
before_save :encrypt_password
|
before_save :encrypt_password
|
||||||
after_save {|rec| Cache.put("user_name:#{rec.id}", rec.name)}
|
after_save {|rec| Cache.put("user_name:#{rec.id}", rec.name)}
|
||||||
|
|
||||||
scope :named, lambda {|name| where(["lower(name) = ?", name])}
|
scope :named, lambda {|name| where(["lower(name) = ?", name])}
|
||||||
|
|
||||||
def can_update?(object, foreign_key = :user_id)
|
def can_update?(object, foreign_key = :user_id)
|
||||||
is_moderator? || is_admin? || object.__send__(foreign_key) == id
|
is_moderator? || is_admin? || object.__send__(foreign_key) == id
|
||||||
end
|
end
|
||||||
|
|
||||||
### Name Methods ###
|
module NameMethods
|
||||||
def self.find_name(user_id)
|
module ClassMethods
|
||||||
Cache.get("user_name:#{user_id}") do
|
def find_name(user_id)
|
||||||
select_value_sql("SELECT name FROM users WHERE id = ?", user_id) || Danbooru.config.default_guest_name
|
Cache.get("user_name:#{user_id}", 24.hours) do
|
||||||
|
select_value_sql("SELECT name FROM users WHERE id = ?", user_id) || Danbooru.config.default_guest_name
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
def self.included(m)
|
||||||
def pretty_name
|
m.extend(ClassMethods)
|
||||||
name.tr("_", " ")
|
|
||||||
end
|
|
||||||
|
|
||||||
### Password Methods ###
|
|
||||||
def encrypt_password
|
|
||||||
self.password_hash = self.class.sha1(password) if password
|
|
||||||
end
|
|
||||||
|
|
||||||
def reset_password
|
|
||||||
consonants = "bcdfghjklmnpqrstvqxyz"
|
|
||||||
vowels = "aeiou"
|
|
||||||
pass = ""
|
|
||||||
|
|
||||||
4.times do
|
|
||||||
pass << consonants[rand(21), 1]
|
|
||||||
pass << vowels[rand(5), 1]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
pass << rand(100).to_s
|
def pretty_name
|
||||||
execute_sql("UPDATE users SET password_hash = ? WHERE id = ?", self.class.sha1(pass), id)
|
name.tr("_", " ")
|
||||||
pass
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
### Authentication Methods ###
|
module PasswordMethods
|
||||||
def self.authenticate(name, pass)
|
def encrypt_password
|
||||||
authenticate_hash(name, sha1(pass))
|
self.password_hash = self.class.sha1(password) if password
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.authenticate_hash(name, pass)
|
def reset_password
|
||||||
where(["lower(name) = ? AND password_hash = ?", name.downcase, pass]).first != nil
|
consonants = "bcdfghjklmnpqrstvqxyz"
|
||||||
end
|
vowels = "aeiou"
|
||||||
|
pass = ""
|
||||||
|
|
||||||
def self.sha1(pass)
|
4.times do
|
||||||
Digest::SHA1.hexdigest("#{Danbooru.config.password_salt}--#{pass}--")
|
pass << consonants[rand(21), 1]
|
||||||
|
pass << vowels[rand(5), 1]
|
||||||
|
end
|
||||||
|
|
||||||
|
pass << rand(100).to_s
|
||||||
|
execute_sql("UPDATE users SET password_hash = ? WHERE id = ?", self.class.sha1(pass), id)
|
||||||
|
pass
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
module AuthenticationMethods
|
||||||
|
def authenticate(name, pass)
|
||||||
|
authenticate_hash(name, sha1(pass))
|
||||||
|
end
|
||||||
|
|
||||||
|
def authenticate_hash(name, pass)
|
||||||
|
where(["lower(name) = ? AND password_hash = ?", name.downcase, pass]).first != nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def sha1(pass)
|
||||||
|
Digest::SHA1.hexdigest("#{Danbooru.config.password_salt}--#{pass}--")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
include NameMethods
|
||||||
|
include PasswordMethods
|
||||||
|
extend AuthenticationMethods
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -113,6 +113,42 @@ CREATE SEQUENCE pending_posts_id_seq
|
|||||||
ALTER SEQUENCE pending_posts_id_seq OWNED BY pending_posts.id;
|
ALTER SEQUENCE pending_posts_id_seq OWNED BY pending_posts.id;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: post_versions; Type: TABLE; Schema: public; Owner: -; Tablespace:
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE post_versions (
|
||||||
|
id integer NOT NULL,
|
||||||
|
created_at timestamp without time zone,
|
||||||
|
updated_at timestamp without time zone,
|
||||||
|
post_id integer NOT NULL,
|
||||||
|
source character varying(255),
|
||||||
|
rating character(1) DEFAULT 'q'::bpchar NOT NULL,
|
||||||
|
tag_string text NOT NULL,
|
||||||
|
updater_id integer NOT NULL,
|
||||||
|
updater_ip_addr inet NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: post_versions_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE SEQUENCE post_versions_id_seq
|
||||||
|
START WITH 1
|
||||||
|
INCREMENT BY 1
|
||||||
|
NO MAXVALUE
|
||||||
|
NO MINVALUE
|
||||||
|
CACHE 1;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: post_versions_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER SEQUENCE post_versions_id_seq OWNED BY post_versions.id;
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: posts; Type: TABLE; Schema: public; Owner: -; Tablespace:
|
-- Name: posts; Type: TABLE; Schema: public; Owner: -; Tablespace:
|
||||||
--
|
--
|
||||||
@@ -129,10 +165,13 @@ CREATE TABLE posts (
|
|||||||
is_rating_locked boolean DEFAULT false NOT NULL,
|
is_rating_locked boolean DEFAULT false NOT NULL,
|
||||||
is_pending boolean DEFAULT false NOT NULL,
|
is_pending boolean DEFAULT false NOT NULL,
|
||||||
is_flagged boolean DEFAULT false NOT NULL,
|
is_flagged boolean DEFAULT false NOT NULL,
|
||||||
approver_id integer,
|
is_deleted boolean DEFAULT false NOT NULL,
|
||||||
change_seq integer DEFAULT 0,
|
uploader_string character varying(255) NOT NULL,
|
||||||
uploader_id integer NOT NULL,
|
|
||||||
uploader_ip_addr inet NOT NULL,
|
uploader_ip_addr inet NOT NULL,
|
||||||
|
approver_string character varying(255) DEFAULT ''::character varying NOT NULL,
|
||||||
|
fav_string text DEFAULT ''::text NOT NULL,
|
||||||
|
pool_string text DEFAULT ''::text NOT NULL,
|
||||||
|
view_count integer DEFAULT 0 NOT NULL,
|
||||||
last_noted_at timestamp without time zone,
|
last_noted_at timestamp without time zone,
|
||||||
last_commented_at timestamp without time zone,
|
last_commented_at timestamp without time zone,
|
||||||
tag_string text NOT NULL,
|
tag_string text NOT NULL,
|
||||||
@@ -143,9 +182,9 @@ CREATE TABLE posts (
|
|||||||
tag_count_character integer DEFAULT 0 NOT NULL,
|
tag_count_character integer DEFAULT 0 NOT NULL,
|
||||||
tag_count_copyright integer DEFAULT 0 NOT NULL,
|
tag_count_copyright integer DEFAULT 0 NOT NULL,
|
||||||
file_ext character varying(255) NOT NULL,
|
file_ext character varying(255) NOT NULL,
|
||||||
|
file_size integer NOT NULL,
|
||||||
image_width integer NOT NULL,
|
image_width integer NOT NULL,
|
||||||
image_height integer NOT NULL,
|
image_height integer NOT NULL
|
||||||
file_size integer NOT NULL
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
@@ -185,6 +224,7 @@ CREATE TABLE tags (
|
|||||||
id integer NOT NULL,
|
id integer NOT NULL,
|
||||||
name character varying(255) NOT NULL,
|
name character varying(255) NOT NULL,
|
||||||
post_count integer DEFAULT 0 NOT NULL,
|
post_count integer DEFAULT 0 NOT NULL,
|
||||||
|
view_count integer DEFAULT 0 NOT NULL,
|
||||||
category integer DEFAULT 0 NOT NULL,
|
category integer DEFAULT 0 NOT NULL,
|
||||||
related_tags text,
|
related_tags text,
|
||||||
created_at timestamp without time zone,
|
created_at timestamp without time zone,
|
||||||
@@ -211,6 +251,40 @@ CREATE SEQUENCE tags_id_seq
|
|||||||
ALTER SEQUENCE tags_id_seq OWNED BY tags.id;
|
ALTER SEQUENCE tags_id_seq OWNED BY tags.id;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: unapprovals; Type: TABLE; Schema: public; Owner: -; Tablespace:
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE unapprovals (
|
||||||
|
id integer NOT NULL,
|
||||||
|
post_id integer NOT NULL,
|
||||||
|
reason text,
|
||||||
|
unapprover_id integer NOT NULL,
|
||||||
|
unapprover_ip_addr inet NOT NULL,
|
||||||
|
created_at timestamp without time zone,
|
||||||
|
updated_at timestamp without time zone
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: unapprovals_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE SEQUENCE unapprovals_id_seq
|
||||||
|
START WITH 1
|
||||||
|
INCREMENT BY 1
|
||||||
|
NO MAXVALUE
|
||||||
|
NO MINVALUE
|
||||||
|
CACHE 1;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: unapprovals_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER SEQUENCE unapprovals_id_seq OWNED BY unapprovals.id;
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: users; Type: TABLE; Schema: public; Owner: -; Tablespace:
|
-- Name: users; Type: TABLE; Schema: public; Owner: -; Tablespace:
|
||||||
--
|
--
|
||||||
@@ -235,6 +309,7 @@ CREATE TABLE users (
|
|||||||
receive_email_notifications boolean DEFAULT false NOT NULL,
|
receive_email_notifications boolean DEFAULT false NOT NULL,
|
||||||
comment_threshold integer DEFAULT (-1) NOT NULL,
|
comment_threshold integer DEFAULT (-1) NOT NULL,
|
||||||
always_resize_images boolean DEFAULT false NOT NULL,
|
always_resize_images boolean DEFAULT false NOT NULL,
|
||||||
|
default_image_size character varying(255) DEFAULT 'medium'::character varying NOT NULL,
|
||||||
favorite_tags text,
|
favorite_tags text,
|
||||||
blacklisted_tags text
|
blacklisted_tags text
|
||||||
);
|
);
|
||||||
@@ -266,6 +341,13 @@ ALTER SEQUENCE users_id_seq OWNED BY users.id;
|
|||||||
ALTER TABLE pending_posts ALTER COLUMN id SET DEFAULT nextval('pending_posts_id_seq'::regclass);
|
ALTER TABLE pending_posts ALTER COLUMN id SET DEFAULT nextval('pending_posts_id_seq'::regclass);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: id; Type: DEFAULT; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE post_versions ALTER COLUMN id SET DEFAULT nextval('post_versions_id_seq'::regclass);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: id; Type: DEFAULT; Schema: public; Owner: -
|
-- Name: id; Type: DEFAULT; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
@@ -280,6 +362,13 @@ ALTER TABLE posts ALTER COLUMN id SET DEFAULT nextval('posts_id_seq'::regclass);
|
|||||||
ALTER TABLE tags ALTER COLUMN id SET DEFAULT nextval('tags_id_seq'::regclass);
|
ALTER TABLE tags ALTER COLUMN id SET DEFAULT nextval('tags_id_seq'::regclass);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: id; Type: DEFAULT; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE unapprovals ALTER COLUMN id SET DEFAULT nextval('unapprovals_id_seq'::regclass);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: id; Type: DEFAULT; Schema: public; Owner: -
|
-- Name: id; Type: DEFAULT; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
@@ -295,6 +384,14 @@ ALTER TABLE ONLY pending_posts
|
|||||||
ADD CONSTRAINT pending_posts_pkey PRIMARY KEY (id);
|
ADD CONSTRAINT pending_posts_pkey PRIMARY KEY (id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: post_versions_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace:
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY post_versions
|
||||||
|
ADD CONSTRAINT post_versions_pkey PRIMARY KEY (id);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: posts_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace:
|
-- Name: posts_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace:
|
||||||
--
|
--
|
||||||
@@ -311,6 +408,14 @@ ALTER TABLE ONLY tags
|
|||||||
ADD CONSTRAINT tags_pkey PRIMARY KEY (id);
|
ADD CONSTRAINT tags_pkey PRIMARY KEY (id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: unapprovals_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace:
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY unapprovals
|
||||||
|
ADD CONSTRAINT unapprovals_pkey PRIMARY KEY (id);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: users_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace:
|
-- Name: users_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace:
|
||||||
--
|
--
|
||||||
@@ -320,17 +425,17 @@ ALTER TABLE ONLY users
|
|||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: index_posts_on_approver_id; Type: INDEX; Schema: public; Owner: -; Tablespace:
|
-- Name: index_post_versions_on_post_id; Type: INDEX; Schema: public; Owner: -; Tablespace:
|
||||||
--
|
--
|
||||||
|
|
||||||
CREATE INDEX index_posts_on_approver_id ON posts USING btree (approver_id);
|
CREATE INDEX index_post_versions_on_post_id ON post_versions USING btree (post_id);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: index_posts_on_change_seq; Type: INDEX; Schema: public; Owner: -; Tablespace:
|
-- Name: index_post_versions_on_updater_id; Type: INDEX; Schema: public; Owner: -; Tablespace:
|
||||||
--
|
--
|
||||||
|
|
||||||
CREATE INDEX index_posts_on_change_seq ON posts USING btree (change_seq);
|
CREATE INDEX index_post_versions_on_updater_id ON post_versions USING btree (updater_id);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
@@ -404,10 +509,10 @@ CREATE INDEX index_posts_on_tags_index ON posts USING gin (tag_index);
|
|||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: index_posts_on_uploader_id; Type: INDEX; Schema: public; Owner: -; Tablespace:
|
-- Name: index_posts_on_view_count; Type: INDEX; Schema: public; Owner: -; Tablespace:
|
||||||
--
|
--
|
||||||
|
|
||||||
CREATE INDEX index_posts_on_uploader_id ON posts USING btree (uploader_id);
|
CREATE INDEX index_posts_on_view_count ON posts USING btree (view_count);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
@@ -417,6 +522,13 @@ CREATE INDEX index_posts_on_uploader_id ON posts USING btree (uploader_id);
|
|||||||
CREATE UNIQUE INDEX index_tags_on_name ON tags USING btree (name);
|
CREATE UNIQUE INDEX index_tags_on_name ON tags USING btree (name);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: index_unapprovals_on_post_id; Type: INDEX; Schema: public; Owner: -; Tablespace:
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX index_unapprovals_on_post_id ON unapprovals USING btree (post_id);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: index_users_on_email; Type: INDEX; Schema: public; Owner: -; Tablespace:
|
-- Name: index_users_on_email; Type: INDEX; Schema: public; Owner: -; Tablespace:
|
||||||
--
|
--
|
||||||
@@ -445,7 +557,7 @@ CREATE UNIQUE INDEX unique_schema_migrations ON schema_migrations USING btree (v
|
|||||||
CREATE TRIGGER trigger_posts_on_tag_index_update
|
CREATE TRIGGER trigger_posts_on_tag_index_update
|
||||||
BEFORE INSERT OR UPDATE ON posts
|
BEFORE INSERT OR UPDATE ON posts
|
||||||
FOR EACH ROW
|
FOR EACH ROW
|
||||||
EXECUTE PROCEDURE tsvector_update_trigger('tag_index', 'public.danbooru', 'tag_string');
|
EXECUTE PROCEDURE tsvector_update_trigger('tag_index', 'public.danbooru', 'tag_string', 'fav_string', 'pool_string', 'uploader_string', 'approver_string');
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
@@ -458,4 +570,8 @@ INSERT INTO schema_migrations (version) VALUES ('20100204214746');
|
|||||||
|
|
||||||
INSERT INTO schema_migrations (version) VALUES ('20100205162521');
|
INSERT INTO schema_migrations (version) VALUES ('20100205162521');
|
||||||
|
|
||||||
INSERT INTO schema_migrations (version) VALUES ('20100205224030');
|
INSERT INTO schema_migrations (version) VALUES ('20100205163027');
|
||||||
|
|
||||||
|
INSERT INTO schema_migrations (version) VALUES ('20100205224030');
|
||||||
|
|
||||||
|
INSERT INTO schema_migrations (version) VALUES ('20100209201251');
|
||||||
@@ -23,6 +23,7 @@ class CreateUsers < ActiveRecord::Migration
|
|||||||
t.column :receive_email_notifications, :boolean, :null => false, :default => false
|
t.column :receive_email_notifications, :boolean, :null => false, :default => false
|
||||||
t.column :comment_threshold, :integer, :null => false, :default => -1
|
t.column :comment_threshold, :integer, :null => false, :default => -1
|
||||||
t.column :always_resize_images, :boolean, :null => false, :default => false
|
t.column :always_resize_images, :boolean, :null => false, :default => false
|
||||||
|
t.column :default_image_size, :string, :null => false, :default => "medium"
|
||||||
t.column :favorite_tags, :text
|
t.column :favorite_tags, :text
|
||||||
t.column :blacklisted_tags, :text
|
t.column :blacklisted_tags, :text
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -7,25 +7,34 @@ class CreatePosts < ActiveRecord::Migration
|
|||||||
t.column :source, :string
|
t.column :source, :string
|
||||||
t.column :md5, :string, :null => false
|
t.column :md5, :string, :null => false
|
||||||
t.column :rating, :character, :null => false, :default => 'q'
|
t.column :rating, :character, :null => false, :default => 'q'
|
||||||
|
|
||||||
|
# Statuses
|
||||||
t.column :is_note_locked, :boolean, :null => false, :default => false
|
t.column :is_note_locked, :boolean, :null => false, :default => false
|
||||||
t.column :is_rating_locked, :boolean, :null => false, :default => false
|
t.column :is_rating_locked, :boolean, :null => false, :default => false
|
||||||
t.column :is_pending, :boolean, :null => false, :default => false
|
t.column :is_pending, :boolean, :null => false, :default => false
|
||||||
t.column :is_flagged, :boolean, :null => false, :default => false
|
t.column :is_flagged, :boolean, :null => false, :default => false
|
||||||
t.column :is_deleted, :boolean, :null => false, :default => false
|
t.column :is_deleted, :boolean, :null => false, :default => false
|
||||||
t.column :approver_id, :integer
|
|
||||||
t.column :change_seq, :integer, :default => "nextval('post_change_seq'::regclass)"
|
|
||||||
|
|
||||||
# Uploader
|
# Uploader
|
||||||
t.column :uploader_id, :integer, :null => false
|
t.column :uploader_string, :string, :null => false
|
||||||
t.column :uploader_ip_addr, "inet", :null => false
|
t.column :uploader_ip_addr, "inet", :null => false
|
||||||
|
|
||||||
|
# Approver
|
||||||
|
t.column :approver_string, :string, :null => false, :default => ""
|
||||||
|
|
||||||
|
# Favorites
|
||||||
|
t.column :fav_string, :text, :null => false, :default => ""
|
||||||
|
|
||||||
|
# Pools
|
||||||
|
t.column :pool_string, :text, :null => false, :default => ""
|
||||||
|
|
||||||
# Cached
|
# Cached
|
||||||
t.column :fav_count, :integer
|
t.column :view_count, :integer, :null => false, :default => 0
|
||||||
t.column :last_noted_at, :datetime
|
t.column :last_noted_at, :datetime
|
||||||
t.column :last_commented_at, :datetime
|
t.column :last_commented_at, :datetime
|
||||||
|
|
||||||
# Tags
|
# Tags
|
||||||
t.column :tag_string, :text, :null => false
|
t.column :tag_string, :text, :null => false, :default => ""
|
||||||
t.column :tag_index, "tsvector"
|
t.column :tag_index, "tsvector"
|
||||||
t.column :tag_count, :integer, :null => false, :default => 0
|
t.column :tag_count, :integer, :null => false, :default => 0
|
||||||
t.column :tag_count_general, :integer, :null => false, :default => 0
|
t.column :tag_count_general, :integer, :null => false, :default => 0
|
||||||
@@ -35,22 +44,20 @@ class CreatePosts < ActiveRecord::Migration
|
|||||||
|
|
||||||
# File
|
# File
|
||||||
t.column :file_ext, :string, :null => false
|
t.column :file_ext, :string, :null => false
|
||||||
|
t.column :file_size, :integer, :null => false
|
||||||
t.column :image_width, :integer, :null => false
|
t.column :image_width, :integer, :null => false
|
||||||
t.column :image_height, :integer, :null => false
|
t.column :image_height, :integer, :null => false
|
||||||
t.column :file_size, :integer, :null => false
|
|
||||||
end
|
end
|
||||||
|
|
||||||
add_index :posts, :md5, :unique => true
|
add_index :posts, :md5, :unique => true
|
||||||
add_index :posts, :created_at
|
add_index :posts, :created_at
|
||||||
add_index :posts, :last_commented_at
|
add_index :posts, :last_commented_at
|
||||||
add_index :posts, :last_noted_at
|
add_index :posts, :last_noted_at
|
||||||
add_index :posts, :uploader_id
|
|
||||||
add_index :posts, :approver_id
|
|
||||||
add_index :posts, :change_seq
|
|
||||||
add_index :posts, :file_size
|
add_index :posts, :file_size
|
||||||
add_index :posts, :image_width
|
add_index :posts, :image_width
|
||||||
add_index :posts, :image_height
|
add_index :posts, :image_height
|
||||||
add_index :posts, :source
|
add_index :posts, :source
|
||||||
|
add_index :posts, :view_count
|
||||||
|
|
||||||
execute "CREATE INDEX index_posts_on_mpixels ON posts (((image_width * image_height)::numeric / 1000000.0))"
|
execute "CREATE INDEX index_posts_on_mpixels ON posts (((image_width * image_height)::numeric / 1000000.0))"
|
||||||
|
|
||||||
@@ -89,7 +96,7 @@ class CreatePosts < ActiveRecord::Migration
|
|||||||
execute "CREATE TEXT SEARCH CONFIGURATION public.danbooru (PARSER = public.testparser)"
|
execute "CREATE TEXT SEARCH CONFIGURATION public.danbooru (PARSER = public.testparser)"
|
||||||
execute "ALTER TEXT SEARCH CONFIGURATION public.danbooru ADD MAPPING FOR WORD WITH SIMPLE"
|
execute "ALTER TEXT SEARCH CONFIGURATION public.danbooru ADD MAPPING FOR WORD WITH SIMPLE"
|
||||||
execute "SET default_text_search_config = 'public.danbooru'"
|
execute "SET default_text_search_config = 'public.danbooru'"
|
||||||
execute "CREATE TRIGGER trigger_posts_on_tag_index_update BEFORE INSERT OR UPDATE ON posts FOR EACH ROW EXECUTE PROCEDURE tsvector_update_trigger('tag_index', 'public.danbooru', 'tag_string')"
|
execute "CREATE TRIGGER trigger_posts_on_tag_index_update BEFORE INSERT OR UPDATE ON posts FOR EACH ROW EXECUTE PROCEDURE tsvector_update_trigger('tag_index', 'public.danbooru', 'tag_string', 'fav_string', 'pool_string', 'uploader_string', 'approver_string')"
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.down
|
def self.down
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ class CreateTags < ActiveRecord::Migration
|
|||||||
create_table :tags do |t|
|
create_table :tags do |t|
|
||||||
t.column :name, :string, :null => false
|
t.column :name, :string, :null => false
|
||||||
t.column :post_count, :integer, :null => false, :default => 0
|
t.column :post_count, :integer, :null => false, :default => 0
|
||||||
|
t.column :view_count, :integer, :null => false, :default => 0
|
||||||
t.column :category, :integer, :null => false, :default => 0
|
t.column :category, :integer, :null => false, :default => 0
|
||||||
t.column :related_tags, :text
|
t.column :related_tags, :text
|
||||||
t.timestamps
|
t.timestamps
|
||||||
|
|||||||
26
db/migrate/20100205163027_create_post_versions.rb
Normal file
26
db/migrate/20100205163027_create_post_versions.rb
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
class CreatePostVersions < ActiveRecord::Migration
|
||||||
|
def self.up
|
||||||
|
create_table :post_versions do |t|
|
||||||
|
t.timestamps
|
||||||
|
|
||||||
|
# Post
|
||||||
|
t.column :post_id, :integer, :null => false
|
||||||
|
|
||||||
|
# Versioned
|
||||||
|
t.column :source, :string
|
||||||
|
t.column :rating, :character, :null => false, :default => 'q'
|
||||||
|
t.column :tag_string, :text, :null => false
|
||||||
|
|
||||||
|
# Updater
|
||||||
|
t.column :updater_id, :integer, :null => false
|
||||||
|
t.column :updater_ip_addr, "inet", :null => false
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index :post_versions, :post_id
|
||||||
|
add_index :post_versions, :updater_id
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.down
|
||||||
|
drop_table :post_versions
|
||||||
|
end
|
||||||
|
end
|
||||||
17
db/migrate/20100209201251_create_unapprovals.rb
Normal file
17
db/migrate/20100209201251_create_unapprovals.rb
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
class CreateUnapprovals < ActiveRecord::Migration
|
||||||
|
def self.up
|
||||||
|
create_table :unapprovals do |t|
|
||||||
|
t.column :post_id, :integer, :null => false
|
||||||
|
t.column :reason, :text
|
||||||
|
t.column :unapprover_id, :integer, :null => false
|
||||||
|
t.column :unapprover_ip_addr, "inet", :null => false
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index :unapprovals, :post_id
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.down
|
||||||
|
drop_table :unapprovals
|
||||||
|
end
|
||||||
|
end
|
||||||
13
lib/cache.rb
13
lib/cache.rb
@@ -14,16 +14,13 @@ module Cache
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def incr(key)
|
def incr(key, expiry = 0)
|
||||||
val = Cache.get(key)
|
val = Cache.get(key, expiry)
|
||||||
Cache.put(key, val.to_i + 1)
|
Cache.put(key, val.to_i + 1)
|
||||||
ActiveRecord::Base.logger.debug('MemCache Incr %s' % [key])
|
ActiveRecord::Base.logger.debug('MemCache Incr %s' % [key])
|
||||||
end
|
end
|
||||||
|
|
||||||
def get(key, expiry = 0)
|
def get(key, expiry = 0)
|
||||||
key.gsub!(/\s/, "_")
|
|
||||||
key = key[0, 200]
|
|
||||||
|
|
||||||
if block_given?
|
if block_given?
|
||||||
return yield
|
return yield
|
||||||
else
|
else
|
||||||
@@ -79,8 +76,8 @@ module Cache
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def sanitize_key(key)
|
def sanitize(key)
|
||||||
key.gsub(/\W/, "_").slice(0, 220)
|
key.gsub(/\W/) {|x| "%#{x.ord}"}.slice(0, 240)
|
||||||
end
|
end
|
||||||
|
|
||||||
module_function :get
|
module_function :get
|
||||||
@@ -88,5 +85,5 @@ module Cache
|
|||||||
module_function :incr
|
module_function :incr
|
||||||
module_function :put
|
module_function :put
|
||||||
module_function :delete
|
module_function :delete
|
||||||
module_function :sanitize_key
|
module_function :sanitize
|
||||||
end
|
end
|
||||||
|
|||||||
6
lib/tasks/db_test_reset.rake
Normal file
6
lib/tasks/db_test_reset.rake
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace :db do
|
||||||
|
namespace :test do
|
||||||
|
task :reset => [:environment, "db:drop", "db:create", "db:migrate", "db:structure:dump", "db:test:clone_structure"] do
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
12
test/factories/post.rb
Normal file
12
test/factories/post.rb
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
Factory.define(:post) do |f|
|
||||||
|
f.md5 "abcd"
|
||||||
|
f.uploader {|x| x.association(:user)}
|
||||||
|
f.uploader_ip_addr "127.0.0.1"
|
||||||
|
f.tag_string "tag1 tag2"
|
||||||
|
f.tag_count 2
|
||||||
|
f.tag_count_general 2
|
||||||
|
f.file_ext "jpg"
|
||||||
|
f.image_width 100
|
||||||
|
f.image_height 200
|
||||||
|
f.file_size 2000
|
||||||
|
end
|
||||||
@@ -8,3 +8,11 @@ end
|
|||||||
Factory.define(:artist_tag, :parent => :tag) do |f|
|
Factory.define(:artist_tag, :parent => :tag) do |f|
|
||||||
f.category Tag.categories.artist
|
f.category Tag.categories.artist
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Factory.define(:copyright_tag, :parent => :tag) do |f|
|
||||||
|
f.category Tag.categories.copyright
|
||||||
|
end
|
||||||
|
|
||||||
|
Factory.define(:character_tag, :parent => :tag) do |f|
|
||||||
|
f.category Tag.categories.character
|
||||||
|
end
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ Factory.define(:user) do |f|
|
|||||||
f.name {Faker::Name.first_name}
|
f.name {Faker::Name.first_name}
|
||||||
f.password_hash {User.sha1("password")}
|
f.password_hash {User.sha1("password")}
|
||||||
f.email {Faker::Internet.email}
|
f.email {Faker::Internet.email}
|
||||||
|
f.default_image_size "medium"
|
||||||
end
|
end
|
||||||
|
|
||||||
Factory.define(:banned_user, :parent => :user) do |f|
|
Factory.define(:banned_user, :parent => :user) do |f|
|
||||||
|
|||||||
@@ -1,6 +1,31 @@
|
|||||||
require File.dirname(__FILE__) + '/../test_helper'
|
require File.dirname(__FILE__) + '/../test_helper'
|
||||||
|
|
||||||
class TagTest < ActiveSupport::TestCase
|
class TagTest < ActiveSupport::TestCase
|
||||||
|
context "A tag category fetcher" do
|
||||||
|
setup do
|
||||||
|
MEMCACHE.flush_all
|
||||||
|
end
|
||||||
|
|
||||||
|
should "fetch for a single tag" do
|
||||||
|
Factory.create(:artist_tag, :name => "test")
|
||||||
|
assert_equal(Tag.categories.artist, Tag.category_for("test"))
|
||||||
|
end
|
||||||
|
|
||||||
|
should "fetch for a single tag with strange markup" do
|
||||||
|
Factory.create(:artist_tag, :name => "!@$%")
|
||||||
|
assert_equal(Tag.categories.artist, Tag.category_for("!@$%"))
|
||||||
|
end
|
||||||
|
|
||||||
|
should "fetch for multiple tags" do
|
||||||
|
Factory.create(:artist_tag, :name => "aaa")
|
||||||
|
Factory.create(:copyright_tag, :name => "bbb")
|
||||||
|
categories = Tag.categories_for(%w(aaa bbb ccc))
|
||||||
|
assert_equal(Tag.categories.artist, categories["aaa"])
|
||||||
|
assert_equal(Tag.categories.copyright, categories["bbb"])
|
||||||
|
assert_equal(0, categories["ccc"])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context "A tag category mapping" do
|
context "A tag category mapping" do
|
||||||
setup do
|
setup do
|
||||||
MEMCACHE.flush_all
|
MEMCACHE.flush_all
|
||||||
@@ -51,17 +76,12 @@ class TagTest < ActiveSupport::TestCase
|
|||||||
assert_equal("Artist", @tag.category_name)
|
assert_equal("Artist", @tag.category_name)
|
||||||
end
|
end
|
||||||
|
|
||||||
should "know its cache safe name" do
|
should "reset its category after updating" do
|
||||||
tag = Tag.new
|
tag = Factory.create(:artist_tag)
|
||||||
|
assert_equal(Tag.categories.artist, MEMCACHE.get("tc:#{tag.name}"))
|
||||||
tag.name = "tag"
|
|
||||||
assert_equal("tag", tag.cache_safe_name)
|
tag.update_attribute(:category, Tag.categories.copyright)
|
||||||
|
assert_equal(Tag.categories.copyright, MEMCACHE.get("tc:#{tag.name}"))
|
||||||
tag.name = "tag%"
|
|
||||||
assert_equal("tag_", tag.cache_safe_name)
|
|
||||||
|
|
||||||
tag.name = "tag%%"
|
|
||||||
assert_equal("tag__", tag.cache_safe_name)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
8
test/unit/unapproval_test.rb
Normal file
8
test/unit/unapproval_test.rb
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
class PostUnapprovalTest < ActiveSupport::TestCase
|
||||||
|
# Replace this with your real tests.
|
||||||
|
test "the truth" do
|
||||||
|
assert true
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user