more migrating views over to new unified schema
This commit is contained in:
15
Rakefile
15
Rakefile
@@ -94,11 +94,14 @@ task :reverse_csv do
|
||||
end
|
||||
|
||||
task migrate_domain: :environment do
|
||||
# Domain::MigrateToDomain.new.migrate_e621_users
|
||||
# Domain::MigrateToDomain.new.migrate_e621_posts
|
||||
# Domain::MigrateToDomain.new.migrate_fa_users
|
||||
Domain::MigrateToDomain.new.migrate_e621_users
|
||||
Domain::MigrateToDomain.new.migrate_e621_posts
|
||||
Domain::MigrateToDomain.new.migrate_fa_users
|
||||
Domain::MigrateToDomain.new.migrate_fa_posts
|
||||
# Domain::MigrateToDomain.new.migrate_e621_users_favs
|
||||
# Domain::MigrateToDomain.new.migrate_fa_users_favs
|
||||
# Domain::MigrateToDomain.new.migrate_fa_users_followers
|
||||
Domain::MigrateToDomain.new.migrate_e621_users_favs
|
||||
Domain::MigrateToDomain.new.migrate_fa_users_favs
|
||||
Domain::MigrateToDomain.new.migrate_fa_users_followed_users
|
||||
Domain::MigrateToDomain.new.migrate_inkbunny_users
|
||||
Domain::MigrateToDomain.new.migrate_inkbunny_posts
|
||||
Domain::MigrateToDomain.new.migrate_inkbunny_pools
|
||||
end
|
||||
|
||||
19
app/assets/images/generic-domain.svg
Normal file
19
app/assets/images/generic-domain.svg
Normal file
@@ -0,0 +1,19 @@
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<!-- Background circle -->
|
||||
<circle cx="8" cy="8" r="7" fill="#E0E0E0" />
|
||||
|
||||
<!-- Stylized "www" text -->
|
||||
<path
|
||||
d="M4 8.5C4 6.5 5 5.5 6 5.5C7 5.5 8 6.5 8 8.5C8 6.5 9 5.5 10 5.5C11 5.5 12 6.5 12 8.5"
|
||||
stroke="#666666"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
fill="none"
|
||||
/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 414 B |
@@ -45,12 +45,12 @@ class Domain::Fa::PostsController < ApplicationController
|
||||
post = Domain::Fa::Post.find_by(fa_id: fa_id)
|
||||
enqueued = try_enqueue_post_scan(post, fa_id)
|
||||
|
||||
if post && (file = post.file).present?
|
||||
if post && (file = post.file).present? && created_at = file.created_at
|
||||
state_string =
|
||||
"downloaded #{helpers.time_ago_in_words(file.created_at, include_seconds: true)} ago"
|
||||
elsif post && post.scanned?
|
||||
"downloaded #{helpers.time_ago_in_words(created_at, include_seconds: true)} ago"
|
||||
elsif post && post.scanned? && scanned_at = post.scanned_at
|
||||
state_string =
|
||||
"scanned #{helpers.time_ago_in_words(post.scanned_at, include_seconds: true)} ago"
|
||||
"scanned #{helpers.time_ago_in_words(scanned_at, include_seconds: true)} ago"
|
||||
else
|
||||
state_string = []
|
||||
!post ? state_string << "not seen" : state_string << "#{post.state}"
|
||||
|
||||
44
app/controllers/domain/posts_controller.rb
Normal file
44
app/controllers/domain/posts_controller.rb
Normal file
@@ -0,0 +1,44 @@
|
||||
# typed: true
|
||||
class Domain::PostsController < ApplicationController
|
||||
before_action :set_post, only: %i[show]
|
||||
|
||||
# GET /domain/posts/:id
|
||||
def show
|
||||
authorize @post
|
||||
end
|
||||
|
||||
def index
|
||||
authorize Domain::Post
|
||||
@posts =
|
||||
policy_scope(Domain::Post)
|
||||
.order(created_at: :desc)
|
||||
.page(params[:page])
|
||||
.per(50)
|
||||
.without_count
|
||||
active_sources = (params[:sources] || SourceHelper.all_source_names)
|
||||
unless SourceHelper.has_all_sources?(active_sources)
|
||||
postable_types = SourceHelper.source_names_to_class_names(active_sources)
|
||||
@posts = @posts.where(type: postable_types) if postable_types.any?
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_post
|
||||
type, pk = params[:id].split("/")
|
||||
if type.nil? || pk.nil?
|
||||
# return 404
|
||||
render status: :not_found
|
||||
end
|
||||
|
||||
@post =
|
||||
case type
|
||||
when "fa"
|
||||
Domain::Post::FaPost.find_by(fa_id: pk)
|
||||
when "e621"
|
||||
Domain::Post::E621Post.find_by(e621_id: pk)
|
||||
when "ib"
|
||||
Domain::Post::InkbunnyPost.find_by(ib_post_id: pk)
|
||||
end || raise(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
end
|
||||
29
app/controllers/domain/users_controller.rb
Normal file
29
app/controllers/domain/users_controller.rb
Normal file
@@ -0,0 +1,29 @@
|
||||
# typed: true
|
||||
class Domain::UsersController < ApplicationController
|
||||
before_action :set_user, only: %i[show]
|
||||
skip_before_action :authenticate_user!, only: %i[show]
|
||||
|
||||
def show
|
||||
authorize @user
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_user
|
||||
type, pk = params[:id].split("/")
|
||||
if type.nil? || pk.nil?
|
||||
# return 404
|
||||
render status: :not_found
|
||||
end
|
||||
|
||||
@user =
|
||||
case type
|
||||
when "fa"
|
||||
Domain::User::FaUser.find_by(url_name: pk)
|
||||
when "e621"
|
||||
Domain::User::E621User.find_by(e621_id: pk)
|
||||
when "ib"
|
||||
Domain::User::InkbunnyUser.find_by(name: pk)
|
||||
end || raise(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
end
|
||||
62
app/helpers/domain/posts_helper.rb
Normal file
62
app/helpers/domain/posts_helper.rb
Normal file
@@ -0,0 +1,62 @@
|
||||
# typed: strict
|
||||
module Domain::PostsHelper
|
||||
extend T::Sig
|
||||
extend T::Helpers
|
||||
include HelpersInterface
|
||||
abstract!
|
||||
|
||||
class DomainData < T::Struct
|
||||
const :domain_icon_path, String
|
||||
const :domain_icon_title, String
|
||||
end
|
||||
|
||||
DEFAULT_DOMAIN_DATA =
|
||||
DomainData.new(
|
||||
domain_icon_path: "generic-domain.svg",
|
||||
domain_icon_title: "Unknown",
|
||||
)
|
||||
|
||||
DOMAIN_DATA =
|
||||
T.let(
|
||||
{
|
||||
Domain::Post::FaPost =>
|
||||
DomainData.new(
|
||||
domain_icon_path: "domain-icons/fa.png",
|
||||
domain_icon_title: "Furaffinity",
|
||||
),
|
||||
Domain::Post::E621Post =>
|
||||
DomainData.new(
|
||||
domain_icon_path: "domain-icons/e621.png",
|
||||
domain_icon_title: "E621",
|
||||
),
|
||||
Domain::Post::InkbunnyPost =>
|
||||
DomainData.new(
|
||||
domain_icon_path: "domain-icons/inkbunny.png",
|
||||
domain_icon_title: "Inkbunny",
|
||||
),
|
||||
},
|
||||
T::Hash[T.class_of(Domain::Post), DomainData],
|
||||
)
|
||||
|
||||
sig { params(post: Domain::Post).returns(String) }
|
||||
def domain_post_domain_icon_path(post)
|
||||
post_class = post.class
|
||||
path =
|
||||
if (domain_data = DOMAIN_DATA[post_class])
|
||||
domain_data.domain_icon_path
|
||||
else
|
||||
DEFAULT_DOMAIN_DATA.domain_icon_path
|
||||
end
|
||||
asset_path(path)
|
||||
end
|
||||
|
||||
sig { params(post: Domain::Post).returns(String) }
|
||||
def domain_post_domain_icon_title(post)
|
||||
post_class = post.class
|
||||
if (domain_data = DOMAIN_DATA[post_class])
|
||||
domain_data.domain_icon_title
|
||||
else
|
||||
DEFAULT_DOMAIN_DATA.domain_icon_title
|
||||
end
|
||||
end
|
||||
end
|
||||
161
app/helpers/domain/users_helper.rb
Normal file
161
app/helpers/domain/users_helper.rb
Normal file
@@ -0,0 +1,161 @@
|
||||
# typed: strict
|
||||
module Domain::UsersHelper
|
||||
extend T::Sig
|
||||
extend T::Helpers
|
||||
include HelpersInterface
|
||||
abstract!
|
||||
|
||||
sig do
|
||||
params(
|
||||
avatar: T.nilable(Domain::UserAvatar),
|
||||
thumb: T.nilable(String),
|
||||
).returns(String)
|
||||
end
|
||||
def domain_user_avatar_img_src_path(avatar, thumb: nil)
|
||||
if (sha256 = avatar&.log_entry&.response_sha256)
|
||||
blob_path(HexUtil.bin2hex(sha256), format: "jpg", thumb: thumb)
|
||||
else
|
||||
# default / 'not found' avatar image
|
||||
# "/blobs/9080fd4e7e23920eb2dccfe2d86903fc3e748eebb2e5aa8c657bbf6f3d941cdc/contents.jpg"
|
||||
asset_path("user-circle.svg")
|
||||
end
|
||||
end
|
||||
|
||||
sig { params(user: Domain::User).returns(String) }
|
||||
def site_name_for_user(user)
|
||||
case user
|
||||
when Domain::User::E621User
|
||||
"E621"
|
||||
when Domain::User::FaUser
|
||||
"Furaffinity"
|
||||
when Domain::User::InkbunnyUser
|
||||
"Inkbunny"
|
||||
else
|
||||
Kernel.raise "Unknown user type: #{user.class}"
|
||||
end
|
||||
end
|
||||
|
||||
sig { params(user: Domain::User).returns(String) }
|
||||
def site_icon_path_for_user(user)
|
||||
case user
|
||||
when Domain::User::E621User
|
||||
asset_path("domain-icons/e621.png")
|
||||
when Domain::User::FaUser
|
||||
asset_path("domain-icons/fa.png")
|
||||
when Domain::User::InkbunnyUser
|
||||
asset_path("domain-icons/ib.png")
|
||||
else
|
||||
Kernel.raise "Unknown user type: #{user.class}"
|
||||
end
|
||||
end
|
||||
|
||||
sig { params(html: String).returns(String) }
|
||||
def sanitized_user_profile_html(html)
|
||||
# try to preload all the FA usernames in the profile
|
||||
maybe_url_names =
|
||||
Nokogiri
|
||||
.HTML(html)
|
||||
.css("a")
|
||||
.flat_map do |node|
|
||||
node = T.cast(node, Nokogiri::XML::Element)
|
||||
href = T.cast(Addressable::URI.parse(node["href"]), Addressable::URI)
|
||||
right_host = href.host.nil? || href.host == "www.furaffinity.net"
|
||||
right_path = href.path =~ %r{/user/.+}
|
||||
if right_host && right_path
|
||||
[href]
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
.map do |href|
|
||||
T.cast(href.path.split("/")[2]&.downcase, T.nilable(String))
|
||||
end
|
||||
|
||||
preloaded_users =
|
||||
T.cast(
|
||||
Domain::User::FaUser
|
||||
.where(url_name: maybe_url_names)
|
||||
.includes(:avatar)
|
||||
.index_by(&:url_name),
|
||||
T::Hash[String, Domain::User::FaUser],
|
||||
)
|
||||
|
||||
raw Sanitize.fragment(
|
||||
html,
|
||||
elements: %w[br img b i span strong],
|
||||
attributes: {
|
||||
"span" => %w[style],
|
||||
"a" => [],
|
||||
},
|
||||
css: {
|
||||
properties: %w[font-size color],
|
||||
},
|
||||
transformers:
|
||||
Kernel.lambda do |env|
|
||||
return unless env[:node_name] == "a"
|
||||
node = T.cast(env[:node], Nokogiri::XML::Element)
|
||||
href = URI.parse(node["href"])
|
||||
unless href.host == nil || href.host == "www.furaffinity.net"
|
||||
return
|
||||
end
|
||||
return unless href.path =~ %r{/user/.+}
|
||||
url_name = href.path&.split("/")&.[](2)&.downcase
|
||||
return unless url_name.present?
|
||||
Sanitize.node!(
|
||||
node,
|
||||
{ elements: %w[a], attributes: { "a" => %w[href] } },
|
||||
)
|
||||
node["href"] = domain_user_path("fa/#{url_name}")
|
||||
node["class"] = "text-slate-200 underline decoration-slate-200 " +
|
||||
"decoration-dashed decoration-dashed decoration-1"
|
||||
|
||||
whitelist = [node]
|
||||
|
||||
user =
|
||||
preloaded_users[url_name] ||
|
||||
Domain::User::FaUser.find_by(url_name: url_name)
|
||||
if user && (avatar = user.avatar)
|
||||
img = Nokogiri::XML::Node.new("img", node.document)
|
||||
img["class"] = "inline w-5"
|
||||
img["src"] = domain_user_avatar_img_src_path(
|
||||
avatar,
|
||||
thumb: "32-avatar",
|
||||
)
|
||||
node.prepend_child(img)
|
||||
whitelist << img
|
||||
end
|
||||
|
||||
{ node_allowlist: whitelist }
|
||||
end,
|
||||
)
|
||||
end
|
||||
|
||||
sig { params(user: Domain::User).returns(T::Array[[String, Integer]]) }
|
||||
def stat_rows_for_user(user)
|
||||
rows = []
|
||||
rows << ["Favorites", user.user_post_favs.count] if user.has_faved_posts?
|
||||
if user.has_followed_users?
|
||||
rows << ["Following", user.user_user_follows_from.count]
|
||||
end
|
||||
if user.has_followed_by_users?
|
||||
rows << ["Followed by", user.user_user_follows_to.count]
|
||||
end
|
||||
|
||||
can_view_timestamps = policy(user).view_page_scanned_at_timestamps?
|
||||
|
||||
if user.is_a?(Domain::User::FaUser) && can_view_timestamps
|
||||
rows << ["Favorites", time_ago_or_never(user.scanned_gallery_at)]
|
||||
rows << ["Gallery scanned", time_ago_or_never(user.scanned_gallery_at)]
|
||||
rows << ["Page scanned", time_ago_or_never(user.scanned_page_at)]
|
||||
elsif user.is_a?(Domain::User::E621User) && can_view_timestamps
|
||||
if user.favs_are_hidden
|
||||
rows << ["Favorites hidden", "yes"]
|
||||
else
|
||||
rows << ["Server favorites", user.num_other_favs_cached]
|
||||
end
|
||||
rows << ["Favorites scanned", time_ago_or_never(user.scanned_favs_at)]
|
||||
end
|
||||
|
||||
rows
|
||||
end
|
||||
end
|
||||
48
app/helpers/helpers_interface.rb
Normal file
48
app/helpers/helpers_interface.rb
Normal file
@@ -0,0 +1,48 @@
|
||||
# typed: strict
|
||||
module HelpersInterface
|
||||
extend T::Sig
|
||||
extend T::Helpers
|
||||
abstract!
|
||||
|
||||
sig do
|
||||
params(timestamp: T.nilable(ActiveSupport::TimeWithZone)).returns(String)
|
||||
end
|
||||
def time_ago_or_never(timestamp)
|
||||
timestamp ? time_ago_in_words(timestamp) + " ago" : "never"
|
||||
end
|
||||
|
||||
sig do
|
||||
abstract
|
||||
.params(sha256: String, format: String, thumb: T.nilable(String))
|
||||
.returns(String)
|
||||
end
|
||||
def blob_path(sha256, format:, thumb: nil)
|
||||
end
|
||||
|
||||
sig { abstract.params(path: String).returns(String) }
|
||||
def asset_path(path)
|
||||
end
|
||||
|
||||
sig { abstract.params(value: String).returns(String) }
|
||||
def raw(value)
|
||||
end
|
||||
|
||||
sig { abstract.params(id: String).returns(String) }
|
||||
def domain_user_path(id)
|
||||
end
|
||||
|
||||
sig do
|
||||
abstract
|
||||
.params(
|
||||
timestamp: ActiveSupport::TimeWithZone,
|
||||
include_seconds: T::Boolean,
|
||||
)
|
||||
.returns(String)
|
||||
end
|
||||
def time_ago_in_words(timestamp, include_seconds: false)
|
||||
end
|
||||
|
||||
sig { abstract.params(user: Domain::User).returns(Domain::UserPolicy) }
|
||||
def policy(user)
|
||||
end
|
||||
end
|
||||
@@ -2,116 +2,79 @@
|
||||
class Domain::Fa::Job::ScanFileJob < Domain::Fa::Job::Base
|
||||
queue_as :static_file
|
||||
|
||||
sig { override.params(args: T::Hash[Symbol, T.untyped]).void }
|
||||
def initialize(*args)
|
||||
super(*T.unsafe(args))
|
||||
@file = T.let(nil, T.nilable(Domain::PostFile))
|
||||
@post = T.let(nil, T.nilable(T.any(Domain::Fa::Post, Domain::Post::FaPost)))
|
||||
end
|
||||
|
||||
sig { override.params(args: T::Hash[Symbol, T.untyped]).void }
|
||||
def perform(args)
|
||||
@file = args[:post_file]
|
||||
@post = args[:post]
|
||||
@force_scan = !!args[:force_scan]
|
||||
file =
|
||||
T.cast(args[:post_file], T.nilable(Domain::PostFile)) ||
|
||||
begin
|
||||
post = args[:post]
|
||||
if post.nil?
|
||||
if args[:fa_id]
|
||||
logger.error "no post model - fa_id: #{args[:fa_id]}, enqueue scan"
|
||||
defer_job(Domain::Fa::Job::ScanPostJob, { fa_id: args[:fa_id] })
|
||||
else
|
||||
fatal_error("no post model or fa_id")
|
||||
end
|
||||
return
|
||||
end
|
||||
post =
|
||||
if post.is_a?(Domain::Fa::Post)
|
||||
Domain::Post::FaPost.find_by!(fa_id: post.fa_id)
|
||||
elsif post.is_a?(Domain::Post::FaPost)
|
||||
post
|
||||
else
|
||||
fatal_error(
|
||||
"invalid post model: #{post.class}, expected Domain::Fa::Post or Domain::Post::FaPost",
|
||||
)
|
||||
raise
|
||||
end
|
||||
post.file
|
||||
end
|
||||
|
||||
if @post.nil?
|
||||
logger.error "no post model - fa_id: #{args[:fa_id]}, enqueue scan"
|
||||
if args[:fa_id]
|
||||
defer_job(Domain::Fa::Job::ScanPostJob, { fa_id: args[:fa_id] })
|
||||
end
|
||||
file = T.must(file)
|
||||
|
||||
logger.info "scanning file: #{file.id}, state=#{file.state}, retry_count=#{file.retry_count}"
|
||||
|
||||
if file.state == "terminal_error" && !@force_scan
|
||||
logger.warn("state == terminal_error, abort without retrying")
|
||||
return
|
||||
end
|
||||
|
||||
post =
|
||||
if @post.is_a?(Domain::Fa::Post)
|
||||
Domain::Post::FaPost.find_by!(fa_id: @post.fa_id)
|
||||
elsif @post.is_a?(Domain::Post::FaPost)
|
||||
@post
|
||||
else
|
||||
fatal_error("invalid post model: #{@post.class}")
|
||||
raise
|
||||
end
|
||||
|
||||
file = post.file
|
||||
if file.nil?
|
||||
logger.error "no file model - fa_id: #{post.fa_id}, enqueue scan"
|
||||
defer_job(Domain::Fa::Job::ScanPostJob, { post: post })
|
||||
return
|
||||
end
|
||||
logger.prefix = "[fa_id #{post.fa_id.to_s.bold} / #{post.state&.bold}]"
|
||||
|
||||
if file.state == "error" && file.url_str.nil?
|
||||
logger.error "removed and has no file, skipping"
|
||||
if file.state == "retryable_error" && file.retry_count >= 3
|
||||
logger.warn("retry_count >= 3, abort without retrying")
|
||||
return
|
||||
end
|
||||
|
||||
if !post.scanned_at.present?
|
||||
logger.error "has not been scanned yet, doing so first"
|
||||
enqueue_post_scan(post)
|
||||
if file.state == "ok" && !@force_scan
|
||||
logger.warn("already have file, skipping")
|
||||
return
|
||||
end
|
||||
|
||||
if file.log_entry.present?
|
||||
logger.warn("already have file")
|
||||
return
|
||||
end
|
||||
|
||||
file_url_str = T.must(file.url_str)
|
||||
file_uri = Addressable::URI.parse(file_url_str)
|
||||
file_uri_host = file_uri&.host
|
||||
if file_uri_host
|
||||
is_unresolvable_host = false
|
||||
is_unresolvable_host ||= file_uri_host == "d9.facdn.net"
|
||||
uri_tld = file_uri_host.split(".").last
|
||||
is_unresolvable_host ||=
|
||||
uri_tld.length >= 6 && file_uri_host.start_with?("d.facdn.net")
|
||||
|
||||
if is_unresolvable_host
|
||||
logger.error("host is #{file_uri_host}, which will not resolve")
|
||||
post.state = "error"
|
||||
post.scan_file_error = "unresolvable host"
|
||||
post.save!
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
if file.state == "error" && !@force_scan
|
||||
logger.warn("state == error, skipping")
|
||||
return
|
||||
file_url_str = file.url_str
|
||||
if file_url_str.nil?
|
||||
fatal_error("file has no url")
|
||||
raise
|
||||
end
|
||||
|
||||
response = http_client.get(file_url_str)
|
||||
|
||||
if response.status_code == 404
|
||||
post.state = "error"
|
||||
post.scan_file_error = "404"
|
||||
post.save!
|
||||
logger.error "404, aborting"
|
||||
return
|
||||
end
|
||||
|
||||
if response.status_code != 200
|
||||
defer_job(
|
||||
Domain::Fa::Job::ScanPostJob,
|
||||
{ post: post, caused_by_entry: response.log_entry, force_scan: true },
|
||||
)
|
||||
|
||||
err_msg =
|
||||
"error downloading - log entry #{response.log_entry.id} / status code #{response.status_code}"
|
||||
post.save!
|
||||
|
||||
if response.status_code == 404 && post.state == "removed"
|
||||
logger.error(err_msg)
|
||||
return
|
||||
else
|
||||
fatal_error(err_msg)
|
||||
end
|
||||
end
|
||||
|
||||
logger.debug "#{HexUtil.humansize(T.must(response.log_entry.response&.size))} / #{response.log_entry.content_type} / #{response.log_entry.response_time_ms} ms"
|
||||
file.state = "ok"
|
||||
file.log_entry = response.log_entry
|
||||
file.save!
|
||||
file.last_status_code = response.status_code
|
||||
|
||||
if response.status_code == 200
|
||||
file.state = "ok"
|
||||
file.retry_count = 0
|
||||
logger.info "response is 200, ok"
|
||||
elsif response.status_code == 404
|
||||
file.state = "terminal_error"
|
||||
logger.error "response is 404, abort without retrying"
|
||||
else
|
||||
file.state = "retryable_error"
|
||||
file.retry_count += 1
|
||||
logger.warn "response is #{response.status_code}, retrying later"
|
||||
# job runner will retry this job as it threw an exception
|
||||
fatal_error("response #{response.status_code}, aborting")
|
||||
end
|
||||
ensure
|
||||
file.save! if file
|
||||
end
|
||||
end
|
||||
|
||||
@@ -21,10 +21,8 @@ class Domain::Fa::Job::ScanPostJob < Domain::Fa::Job::Base
|
||||
end
|
||||
|
||||
file = post.file
|
||||
if (
|
||||
file.present? && file.state == "ok" && file.url_str.present? &&
|
||||
!file.log_entry.present?
|
||||
) || force_scan?
|
||||
if (file.present? && file.state == "pending" && file.url_str.present?) ||
|
||||
force_scan?
|
||||
logger.info("enqueue file job (#{self.priority})")
|
||||
defer_job(
|
||||
Domain::Fa::Job::ScanFileJob,
|
||||
|
||||
@@ -31,7 +31,7 @@ class DbSampler
|
||||
|
||||
sig { void.params(file: T.untyped) }
|
||||
def initialize(file)
|
||||
@file = T.let(file, StringIO)
|
||||
@file = T.let(file, T.any(StringIO, IO))
|
||||
@handled = T.let(Set.new, T::Set[ReduxApplicationRecord])
|
||||
end
|
||||
|
||||
@@ -80,6 +80,7 @@ class DbSampler
|
||||
deferred.each do |model|
|
||||
import_model(model)
|
||||
rescue StandardError
|
||||
$stderr.puts("error importing deferred #{model_id(model)}, skipping")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -105,14 +106,18 @@ class DbSampler
|
||||
$stderr.puts("skipped existing #{model_id(model)}")
|
||||
else
|
||||
model2 = model.class.new
|
||||
model
|
||||
model2
|
||||
.attribute_names
|
||||
.map(&:to_sym)
|
||||
.each do |attr|
|
||||
model2.write_attribute(attr, model.read_attribute(attr))
|
||||
end
|
||||
model2.save(validate: false)
|
||||
$stderr.puts("imported #{model_id(model)}")
|
||||
begin
|
||||
model2.save(validate: false)
|
||||
$stderr.puts("imported #{model_id(model)}")
|
||||
rescue ActiveRecord::RecordNotUnique, PG::UniqueViolation
|
||||
$stderr.puts("skipped #{model_id(model)}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -46,12 +46,23 @@ class Domain::MigrateToDomain
|
||||
format: "%t: %c/%C %B %p%% %a %e",
|
||||
output: @pb_sink,
|
||||
)
|
||||
query.find_in_batches(batch_size: 10_000) do |batch|
|
||||
migrate_batch(Domain::Post::E621Post, batch) do |old_model|
|
||||
initialize_e621_post_from(old_model)
|
||||
query
|
||||
.includes(:file)
|
||||
.find_in_batches(batch_size: 10_000) do |batch|
|
||||
ReduxApplicationRecord.transaction do
|
||||
models =
|
||||
migrate_batch(Domain::Post::E621Post, batch) do |old_model|
|
||||
initialize_e621_post_from(old_model)
|
||||
end
|
||||
|
||||
migrate_batch(Domain::PostFile, models.filter(&:file)) do |post|
|
||||
file = T.must(post.file)
|
||||
file.post_id = T.must(post.id)
|
||||
file
|
||||
end
|
||||
end
|
||||
pb.progress = [pb.progress + batch.size, pb.total].min
|
||||
end
|
||||
pb.progress = [pb.progress + batch.size, pb.total].min
|
||||
end
|
||||
end
|
||||
|
||||
sig { void }
|
||||
@@ -72,15 +83,17 @@ class Domain::MigrateToDomain
|
||||
output: @pb_sink,
|
||||
)
|
||||
query.find_in_batches(batch_size: 10_000) do |batch|
|
||||
models =
|
||||
migrate_batch(Domain::User::FaUser, batch) do |old_user|
|
||||
initialize_fa_user_from(old_user)
|
||||
end
|
||||
ReduxApplicationRecord.transaction do
|
||||
models =
|
||||
migrate_batch(Domain::User::FaUser, batch) do |old_user|
|
||||
initialize_fa_user_from(old_user)
|
||||
end
|
||||
|
||||
migrate_batch(Domain::UserAvatar, models.filter(&:avatar)) do |user|
|
||||
avatar = T.must(user.avatar)
|
||||
avatar.user_id = user.id
|
||||
avatar
|
||||
migrate_batch(Domain::UserAvatar, models.filter(&:avatar)) do |user|
|
||||
avatar = T.must(user.avatar)
|
||||
avatar.user_id = user.id
|
||||
avatar
|
||||
end
|
||||
end
|
||||
|
||||
pb.progress = [pb.progress + batch.size, pb.total].min
|
||||
@@ -106,7 +119,7 @@ class Domain::MigrateToDomain
|
||||
output: @pb_sink,
|
||||
)
|
||||
|
||||
missing_fa_ids.each_slice(10_000) do |fa_ids|
|
||||
missing_fa_ids.each_slice(1_000) do |fa_ids|
|
||||
batch =
|
||||
Domain::Fa::Post.where(fa_id: fa_ids).includes(:creator, :file).to_a
|
||||
ReduxApplicationRecord.transaction do
|
||||
@@ -140,29 +153,54 @@ class Domain::MigrateToDomain
|
||||
sig { void }
|
||||
def migrate_e621_users_favs
|
||||
logger.info "migrating e621 users favs"
|
||||
Domain::User::E621User
|
||||
.where(migrated_user_favs_at: nil)
|
||||
.find_each { |user| migrate_e621_user_favs(user) }
|
||||
query = Domain::User::E621User.where(migrated_user_favs_at: nil)
|
||||
pb =
|
||||
ProgressBar.create(
|
||||
throttle_rate: 0.2,
|
||||
total: query.count,
|
||||
format: "%t: %c/%C %B %p%% %a %e",
|
||||
output: @pb_sink,
|
||||
)
|
||||
query.find_each do |user|
|
||||
ReduxApplicationRecord.transaction { migrate_e621_user_favs(user) }
|
||||
pb.progress = [pb.progress + 1, pb.total].min
|
||||
end
|
||||
end
|
||||
|
||||
sig { void }
|
||||
def migrate_fa_users_favs
|
||||
logger.info "migrating fa users favs"
|
||||
Domain::User::FaUser
|
||||
.where(migrated_user_favs_at: nil)
|
||||
.find_each { |user| migrate_fa_user_favs(user) }
|
||||
query = Domain::User::FaUser.where(migrated_user_favs_at: nil)
|
||||
pb =
|
||||
ProgressBar.create(
|
||||
throttle_rate: 0.2,
|
||||
total: query.count,
|
||||
format: "%t: %c/%C %B %p%% %a %e",
|
||||
output: @pb_sink,
|
||||
)
|
||||
query.find_each do |user|
|
||||
ReduxApplicationRecord.transaction { migrate_fa_user_favs(user) }
|
||||
pb.progress = [pb.progress + 1, pb.total].min
|
||||
end
|
||||
end
|
||||
|
||||
sig { void }
|
||||
def migrate_fa_users_followed_users
|
||||
logger.info "migrating fa followed users"
|
||||
Domain::User::FaUser
|
||||
.where(migrated_followed_users_at: nil)
|
||||
.find_each do |user|
|
||||
ReduxApplicationRecord.transaction do
|
||||
migrate_fa_user_followed_users(user)
|
||||
end
|
||||
query = Domain::User::FaUser.where(migrated_followed_users_at: nil)
|
||||
pb =
|
||||
ProgressBar.create(
|
||||
throttle_rate: 0.2,
|
||||
total: query.count,
|
||||
format: "%t: %c/%C %B %p%% %a %e",
|
||||
output: @pb_sink,
|
||||
)
|
||||
query.find_each do |user|
|
||||
ReduxApplicationRecord.transaction do
|
||||
migrate_fa_user_followed_users(user)
|
||||
end
|
||||
pb.progress = [pb.progress + 1, pb.total].min
|
||||
end
|
||||
end
|
||||
|
||||
sig { void }
|
||||
@@ -203,7 +241,7 @@ class Domain::MigrateToDomain
|
||||
query =
|
||||
Domain::Inkbunny::Post
|
||||
.where.not(ib_post_id: Domain::Post::InkbunnyPost.select(:ib_id))
|
||||
.includes(:creator, :files, :pools)
|
||||
.includes(:creator, { files: :log_entry }, :pools)
|
||||
|
||||
pb =
|
||||
ProgressBar.create(
|
||||
@@ -233,9 +271,10 @@ class Domain::MigrateToDomain
|
||||
model.files.each { |file| file.post_id = model.id }
|
||||
end
|
||||
|
||||
migrate_batch(Domain::PostFile, models.flat_map(&:files)) do |file|
|
||||
file
|
||||
end
|
||||
migrate_batch(
|
||||
Domain::PostFile::InkbunnyPostFile,
|
||||
models.flat_map(&:files),
|
||||
) { |file| file }
|
||||
end
|
||||
pb.progress = [pb.progress + batch.size, pb.total].min
|
||||
end
|
||||
@@ -323,6 +362,17 @@ class Domain::MigrateToDomain
|
||||
new_post.rating = old_post.rating
|
||||
new_post.submission_type = old_post.submission_type
|
||||
new_post.created_at = old_post.created_at
|
||||
new_post.posted_at = old_post.posted_at
|
||||
new_post.title = old_post.title
|
||||
new_post.writing = old_post.writing
|
||||
new_post.num_views = old_post.num_views
|
||||
new_post.num_files = old_post.num_files
|
||||
new_post.num_favs = old_post.num_favs
|
||||
new_post.num_comments = old_post.num_comments
|
||||
new_post.keywords = old_post.keywords
|
||||
new_post.last_file_updated_at = old_post.last_file_updated_at
|
||||
new_post.deep_update_log_entry_id = old_post.deep_update_log_entry_id
|
||||
new_post.shallow_update_log_entry_id = old_post.shallow_update_log_entry_id
|
||||
|
||||
if old_creator = old_post.creator
|
||||
new_post.creator =
|
||||
@@ -331,10 +381,26 @@ class Domain::MigrateToDomain
|
||||
|
||||
new_post.files =
|
||||
old_post.files.map do |old_file|
|
||||
new_file = Domain::PostFile.new
|
||||
new_state =
|
||||
case old_file.state
|
||||
when "ok"
|
||||
old_file.log_entry_id.present? ? "ok" : "pending"
|
||||
else
|
||||
"terminal_error"
|
||||
end
|
||||
new_file = Domain::PostFile::InkbunnyPostFile.new
|
||||
new_file.ib_id = old_file.ib_file_id
|
||||
new_file.log_entry_id = old_file.log_entry_id
|
||||
new_file.last_status_code = old_file.log_entry&.status_code
|
||||
new_file.url_str = old_file.url_str
|
||||
new_file.state = old_file.state
|
||||
new_file.state = new_state
|
||||
new_file.file_name = old_file.file_name
|
||||
new_file.md5_initial = old_file.md5_initial
|
||||
new_file.md5_full = old_file.md5_full
|
||||
new_file.md5s = old_file.md5s
|
||||
new_file.file_order = old_file.file_order
|
||||
new_file.ib_detail_raw = old_file.ib_detail_raw
|
||||
new_file.ib_created_at = old_file.ib_created_at
|
||||
new_file
|
||||
end
|
||||
|
||||
@@ -406,6 +472,7 @@ class Domain::MigrateToDomain
|
||||
new_post.sources_array = old_post.sources_array
|
||||
new_post.artists_array = old_post.artists_array
|
||||
new_post.e621_updated_at = old_post.e621_updated_at
|
||||
new_post.posted_at = old_post.posted_at
|
||||
new_post.last_index_page_id = old_post.last_index_page_id
|
||||
new_post.caused_by_entry_id = old_post.caused_by_entry_id
|
||||
new_post.scan_log_entry_id = old_post.scan_log_entry_id
|
||||
@@ -415,13 +482,43 @@ class Domain::MigrateToDomain
|
||||
new_post.file_error = old_post.file_error
|
||||
new_post.created_at = old_post.created_at
|
||||
new_post.parent_post_e621_id = old_post.parent_e621_id
|
||||
|
||||
old_file = old_post.file
|
||||
file_url_str = old_post.file_url_str
|
||||
if old_file || file_url_str
|
||||
new_file = Domain::PostFile.new
|
||||
new_file.url_str = file_url_str
|
||||
new_file.log_entry = old_file
|
||||
if old_file.present? && old_file.status_code == 200
|
||||
new_file.state = "ok"
|
||||
elsif old_file.present?
|
||||
new_file.state = "terminal_error"
|
||||
new_file.error_message = "status_code: #{old_file.status_code}"
|
||||
else
|
||||
new_file.state = "pending"
|
||||
end
|
||||
new_post.file = new_file
|
||||
end
|
||||
|
||||
new_post
|
||||
end
|
||||
|
||||
sig { params(old_user: Domain::Fa::User).returns(Domain::User::FaUser) }
|
||||
def initialize_fa_user_from(old_user)
|
||||
new_user = Domain::User::FaUser.new
|
||||
new_user.state = old_user.state
|
||||
new_user.state =
|
||||
case old_user.state
|
||||
when "ok"
|
||||
"ok"
|
||||
when "scan_error"
|
||||
if /disabled or not found/ =~ old_user.state_detail["scan_error"]
|
||||
"account_disabled"
|
||||
else
|
||||
"error"
|
||||
end
|
||||
else
|
||||
raise("unknown fa user state: #{old_user.state}")
|
||||
end
|
||||
new_user.url_name = old_user.url_name
|
||||
new_user.name = old_user.name
|
||||
new_user.full_name = old_user.full_name
|
||||
@@ -492,10 +589,10 @@ class Domain::MigrateToDomain
|
||||
Domain::User::FaUser.find_by!(url_name: post.creator&.url_name)
|
||||
end
|
||||
|
||||
if post.file.present?
|
||||
if post.file.present? || post.file_uri.present?
|
||||
new_file = Domain::PostFile.new
|
||||
new_file.log_entry_id = post.file_id
|
||||
new_file.url_str = post.file_url_str
|
||||
new_file.url_str = post.file_uri.to_s
|
||||
new_file.state = post.state
|
||||
new_post.file = new_file
|
||||
end
|
||||
@@ -548,9 +645,6 @@ class Domain::MigrateToDomain
|
||||
else
|
||||
user.migrated_followed_users_at = Time.current
|
||||
user.save!
|
||||
logger.info(
|
||||
"migrated fa user followers #{user.name} (#{new_user_ids.size})",
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -576,9 +670,6 @@ class Domain::MigrateToDomain
|
||||
else
|
||||
new_user.migrated_user_favs_at = Time.current
|
||||
new_user.save!
|
||||
logger.info(
|
||||
"migrated e621 user favs #{new_user.name} (#{new_post_ids.size})",
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -88,13 +88,9 @@ module AttrJsonRecordAliases
|
||||
included { include AttrJson::Record }
|
||||
requires_ancestor { ActiveRecord::Base }
|
||||
|
||||
sig { abstract.returns(T.class_of(ActiveRecord::Base)) }
|
||||
def class
|
||||
end
|
||||
|
||||
sig { params(attributes: T::Hash[Symbol, T.untyped]).returns(T.untyped) }
|
||||
def update_json_columns(attributes)
|
||||
klass = self.class
|
||||
klass = T.cast(self.class, T.class_of(ActiveRecord::Base))
|
||||
updated_json = klass.connection.quote(JSON.dump(attributes))
|
||||
klass.connection.execute(<<~SQL)
|
||||
UPDATE #{klass.quoted_table_name}
|
||||
@@ -105,8 +101,9 @@ module AttrJsonRecordAliases
|
||||
|
||||
sig { params(name: T.untyped, value: T.untyped).returns(T.untyped) }
|
||||
def write_attribute(name, value)
|
||||
klass = T.cast(self.class, T.class_of(ActiveRecord::Base))
|
||||
ret = super(name, value)
|
||||
if attribute_def = ImplHelper.get_json_attr_def(self.class, name)
|
||||
if attribute_def = ImplHelper.get_json_attr_def(klass, name)
|
||||
public_send(attribute_def.container_attribute)[
|
||||
attribute_def.store_key
|
||||
] = read_attribute(name)
|
||||
@@ -154,52 +151,6 @@ module AttrJsonRecordAliases
|
||||
""
|
||||
end
|
||||
end
|
||||
|
||||
# sig do
|
||||
# params(
|
||||
# attr_name: Symbol,
|
||||
# type: T.any(Symbol, ActiveModel::Type::Value),
|
||||
# ).returns(String)
|
||||
# end
|
||||
# def json_attribute_expression(attr_name, type)
|
||||
# adapter_class = T.unsafe(self).adapter_class
|
||||
# "#{adapter_class.quote_table_name(self.table_name)}.#{adapter_class.quote_column_name("json_attributes")}->>'#{attr_name}'"
|
||||
# end
|
||||
|
||||
# sig do
|
||||
# params(
|
||||
# attr_name: Symbol,
|
||||
# type: T.any(Symbol, ActiveModel::Type::Value),
|
||||
# ).void
|
||||
# end
|
||||
# def json_attributes_scope(attr_name, type)
|
||||
# attribute_expression = json_attribute_expression(attr_name, type)
|
||||
# db_type = json_attribute_type_cast(type)
|
||||
# scope :"where_#{attr_name}",
|
||||
# ->(expr, *binds) do
|
||||
# where("((#{attribute_expression})#{db_type}) #{expr}", binds)
|
||||
# end
|
||||
|
||||
# scope :"order_#{attr_name}",
|
||||
# ->(dir) do
|
||||
# unless [:asc, :desc, nil].include?(dir)
|
||||
# raise("invalid direction: #{dir}")
|
||||
# end
|
||||
# order(Arel.sql("#{attribute_expression} #{dir}"))
|
||||
# end
|
||||
# end
|
||||
|
||||
# sig do
|
||||
# params(
|
||||
# attr_name: Symbol,
|
||||
# type: T.any(Symbol, ActiveModel::Type::Value),
|
||||
# options: T.untyped,
|
||||
# ).void
|
||||
# end
|
||||
# def attr_json_scoped(attr_name, type, **options)
|
||||
# T.unsafe(self).attr_json(attr_name, type, **options)
|
||||
# # json_attributes_scope(attr_name, type)
|
||||
# end
|
||||
end
|
||||
|
||||
mixes_in_class_methods(ClassMethods)
|
||||
|
||||
@@ -44,7 +44,10 @@ class Domain::E621::Post < ReduxApplicationRecord
|
||||
foreign_key: :e621_id,
|
||||
optional: true
|
||||
|
||||
has_many :favs, class_name: "Domain::E621::Fav", inverse_of: :post
|
||||
has_many :favs,
|
||||
class_name: "Domain::E621::Fav",
|
||||
inverse_of: :post,
|
||||
dependent: :destroy
|
||||
has_many :faving_users,
|
||||
class_name: "Domain::E621::User",
|
||||
through: :favs,
|
||||
|
||||
@@ -5,6 +5,19 @@ class Domain::Post < ReduxApplicationRecord
|
||||
self.table_name = "domain_posts"
|
||||
abstract!
|
||||
|
||||
class_attribute :class_has_creators, :class_belongs_to_groups
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def self.has_creators?
|
||||
class_has_creators
|
||||
end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def self.belongs_to_groups?
|
||||
class_belongs_to_groups
|
||||
end
|
||||
|
||||
# so sorbet knows this is a string
|
||||
sig { returns(String) }
|
||||
def type
|
||||
super
|
||||
@@ -19,24 +32,29 @@ class Domain::Post < ReduxApplicationRecord
|
||||
def to_param
|
||||
end
|
||||
|
||||
sig { void }
|
||||
def self.has_single_file!
|
||||
# single file association
|
||||
has_one :file,
|
||||
class_name: "::Domain::PostFile",
|
||||
inverse_of: :post,
|
||||
dependent: :destroy
|
||||
end
|
||||
attr_json :posted_at, :datetime
|
||||
|
||||
# multiple files association
|
||||
has_many :files,
|
||||
class_name: "::Domain::PostFile",
|
||||
dependent: :destroy,
|
||||
inverse_of: :post,
|
||||
dependent: :destroy
|
||||
foreign_key: :post_id
|
||||
|
||||
sig { params(klass: T.class_of(Domain::PostFile)).void }
|
||||
def self.has_single_file!(klass = Domain::PostFile)
|
||||
has_one :file, class_name: klass.name, foreign_key: :post_id
|
||||
end
|
||||
|
||||
sig { params(klass: T.class_of(Domain::PostFile)).void }
|
||||
def self.has_multiple_files!(klass = Domain::PostFile)
|
||||
has_many :files,
|
||||
class_name: klass.name,
|
||||
foreign_key: :post_id,
|
||||
inverse_of: :post
|
||||
end
|
||||
|
||||
sig { params(klass: T.class_of(Domain::User)).void }
|
||||
def self.has_single_creator!(klass)
|
||||
# single creator association
|
||||
has_one :primary_user_post_creation,
|
||||
class_name: "::Domain::UserPostCreation",
|
||||
inverse_of: :post,
|
||||
@@ -93,4 +111,34 @@ class Domain::Post < ReduxApplicationRecord
|
||||
source: :group,
|
||||
class_name: group_klass.name
|
||||
end
|
||||
|
||||
sig { abstract.returns(T.nilable(String)) }
|
||||
def title
|
||||
end
|
||||
|
||||
sig { abstract.returns(T.nilable(T.any(String, Integer))) }
|
||||
def domain_id_for_view
|
||||
end
|
||||
|
||||
sig { abstract.returns(String) }
|
||||
def domain_abbreviation_for_view
|
||||
end
|
||||
|
||||
sig { abstract.returns(T.nilable(Addressable::URI)) }
|
||||
def domain_url_for_view
|
||||
end
|
||||
|
||||
sig { abstract.returns(T.nilable(Domain::PostFile)) }
|
||||
def primary_file_for_view
|
||||
end
|
||||
|
||||
sig { overridable.returns(T.nilable(Domain::User)) }
|
||||
def primary_creator_for_view
|
||||
nil
|
||||
end
|
||||
|
||||
sig { overridable.returns(T.nilable(String)) }
|
||||
def primary_creator_name_fallback_for_view
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
@@ -66,4 +66,36 @@ class Domain::Post::E621Post < Domain::Post
|
||||
def to_param
|
||||
"e621/#{self.e621_id}" if self.e621_id.present?
|
||||
end
|
||||
|
||||
sig { override.returns(T.nilable(String)) }
|
||||
def title
|
||||
"E621 Post #{self.e621_id}"
|
||||
end
|
||||
|
||||
sig { override.returns(T.nilable(String)) }
|
||||
def primary_creator_name_fallback_for_view
|
||||
self.tags_array["artist"].first || self.artists_array.first
|
||||
end
|
||||
|
||||
sig { override.returns(T.nilable(T.any(String, Integer))) }
|
||||
def domain_id_for_view
|
||||
self.e621_id
|
||||
end
|
||||
|
||||
sig { override.returns(String) }
|
||||
def domain_abbreviation_for_view
|
||||
"E621"
|
||||
end
|
||||
|
||||
sig { override.returns(T.nilable(Addressable::URI)) }
|
||||
def domain_url_for_view
|
||||
if self.e621_id.present?
|
||||
Addressable::URI.parse("https://e621.net/posts/#{self.e621_id}")
|
||||
end
|
||||
end
|
||||
|
||||
sig { override.returns(T.nilable(Domain::PostFile)) }
|
||||
def primary_file_for_view
|
||||
self.file
|
||||
end
|
||||
end
|
||||
|
||||
@@ -14,7 +14,6 @@ class Domain::Post::FaPost < Domain::Post
|
||||
attr_json :num_favorites, :integer
|
||||
attr_json :num_comments, :integer
|
||||
attr_json :num_views, :integer
|
||||
attr_json :posted_at, :datetime
|
||||
attr_json :scanned_at, :datetime
|
||||
attr_json :scan_file_error, :string
|
||||
attr_json :last_user_page_id, :integer
|
||||
@@ -29,8 +28,8 @@ class Domain::Post::FaPost < Domain::Post
|
||||
belongs_to :first_gallery_page, class_name: "::HttpLogEntry", optional: true
|
||||
belongs_to :first_seen_entry, class_name: "::HttpLogEntry", optional: true
|
||||
|
||||
has_single_creator! Domain::User::FaUser
|
||||
has_single_file!
|
||||
has_single_creator! Domain::User::FaUser
|
||||
has_faving_users! Domain::User::FaUser
|
||||
|
||||
after_initialize { self.state ||= "ok" }
|
||||
@@ -43,6 +42,38 @@ class Domain::Post::FaPost < Domain::Post
|
||||
"fa/#{self.fa_id}" if self.fa_id.present?
|
||||
end
|
||||
|
||||
sig { override.returns(T.nilable(String)) }
|
||||
def title
|
||||
super
|
||||
end
|
||||
|
||||
sig { override.returns(T.nilable(Domain::User)) }
|
||||
def primary_creator_for_view
|
||||
self.creator
|
||||
end
|
||||
|
||||
sig { override.returns(T.nilable(T.any(String, Integer))) }
|
||||
def domain_id_for_view
|
||||
self.fa_id
|
||||
end
|
||||
|
||||
sig { override.returns(String) }
|
||||
def domain_abbreviation_for_view
|
||||
"FA"
|
||||
end
|
||||
|
||||
sig { override.returns(T.nilable(Addressable::URI)) }
|
||||
def domain_url_for_view
|
||||
if self.fa_id.present?
|
||||
Addressable::URI.parse("https://www.furaffinity.net/view/#{self.fa_id}")
|
||||
end
|
||||
end
|
||||
|
||||
sig { override.returns(T.nilable(Domain::PostFile)) }
|
||||
def primary_file_for_view
|
||||
self.file
|
||||
end
|
||||
|
||||
sig do
|
||||
params(
|
||||
submission: Domain::Fa::Parser::ListedSubmissionParserHelper,
|
||||
|
||||
@@ -4,12 +4,29 @@ class Domain::Post::InkbunnyPost < Domain::Post
|
||||
attr_json :state, :string
|
||||
attr_json :rating, :string
|
||||
attr_json :submission_type, :string
|
||||
attr_json :title, :string
|
||||
attr_json :writing, :string
|
||||
attr_json :num_views, :integer
|
||||
attr_json :num_files, :integer
|
||||
attr_json :num_favs, :integer
|
||||
attr_json :num_comments, :integer
|
||||
attr_json :keywords, :string, array: true
|
||||
attr_json :last_file_updated_at, :datetime
|
||||
attr_json :deep_update_log_entry_id, :integer
|
||||
attr_json :shallow_update_log_entry_id, :integer
|
||||
|
||||
has_multiple_files! Domain::PostFile::InkbunnyPostFile
|
||||
has_single_creator! Domain::User::InkbunnyUser
|
||||
has_faving_users! Domain::User::InkbunnyUser
|
||||
belongs_to_groups! :pools,
|
||||
Domain::PostGroup::InkbunnyPool,
|
||||
Domain::PostGroupJoin::InkbunnyPoolJoin
|
||||
belongs_to :deep_update_log_entry,
|
||||
class_name: "::HttpLogEntry",
|
||||
optional: true
|
||||
belongs_to :shallow_update_log_entry,
|
||||
class_name: "::HttpLogEntry",
|
||||
optional: true
|
||||
|
||||
validates :ib_id, presence: true
|
||||
validates :state, presence: true, inclusion: { in: %w[ok error] }
|
||||
@@ -40,4 +57,36 @@ class Domain::Post::InkbunnyPost < Domain::Post
|
||||
def to_param
|
||||
"ib/#{self.ib_id}" if self.ib_id.present?
|
||||
end
|
||||
|
||||
sig { override.returns(T.nilable(String)) }
|
||||
def title
|
||||
super
|
||||
end
|
||||
|
||||
sig { override.returns(T.nilable(Domain::User)) }
|
||||
def primary_creator_for_view
|
||||
self.creator
|
||||
end
|
||||
|
||||
sig { override.returns(T.nilable(T.any(String, Integer))) }
|
||||
def domain_id_for_view
|
||||
self.ib_id
|
||||
end
|
||||
|
||||
sig { override.returns(String) }
|
||||
def domain_abbreviation_for_view
|
||||
"IB"
|
||||
end
|
||||
|
||||
sig { override.returns(T.nilable(Addressable::URI)) }
|
||||
def domain_url_for_view
|
||||
if self.ib_id.present?
|
||||
Addressable::URI.parse("https://inkbunny.net/post/#{self.ib_id}")
|
||||
end
|
||||
end
|
||||
|
||||
sig { override.returns(T.nilable(Domain::PostFile)) }
|
||||
def primary_file_for_view
|
||||
self.files.first
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,14 +3,33 @@ class Domain::PostFile < ReduxApplicationRecord
|
||||
include AttrJsonRecordAliases
|
||||
self.table_name = "domain_post_files"
|
||||
|
||||
belongs_to :post, class_name: "::Domain::Post", inverse_of: :files
|
||||
belongs_to :post, foreign_key: :post_id, class_name: "::Domain::Post"
|
||||
belongs_to :log_entry, class_name: "::HttpLogEntry", optional: true
|
||||
belongs_to :blob,
|
||||
class_name: "::BlobEntry",
|
||||
optional: true,
|
||||
foreign_key: :blob_sha256
|
||||
|
||||
attr_json :state, :string
|
||||
attr_json :url_str, :string
|
||||
attr_json :error_message, :string
|
||||
attr_json :last_status_code, :integer
|
||||
attr_json :retry_count, :integer
|
||||
|
||||
validates :state, inclusion: { in: %w[ok error] }
|
||||
validates :state,
|
||||
inclusion: {
|
||||
in: %w[pending ok retryable_error terminal_error],
|
||||
}
|
||||
|
||||
after_initialize { self.state ||= "ok" }
|
||||
after_initialize do
|
||||
self.state ||= "pending" if new_record?
|
||||
self.type ||= self.class.name if new_record?
|
||||
end
|
||||
|
||||
before_save { self.blob_sha256 ||= self.log_entry&.response_sha256 }
|
||||
|
||||
sig { returns(Integer) }
|
||||
def retry_count
|
||||
super || 0
|
||||
end
|
||||
end
|
||||
|
||||
11
app/models/domain/post_file/inkbunny_post_file.rb
Normal file
11
app/models/domain/post_file/inkbunny_post_file.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
# typed: strict
|
||||
class Domain::PostFile::InkbunnyPostFile < Domain::PostFile
|
||||
attr_json :ib_id, :integer
|
||||
attr_json :ib_detail_raw, ActiveModel::Type::Value.new
|
||||
attr_json :ib_created_at, :datetime
|
||||
attr_json :file_name, :string
|
||||
attr_json :md5_initial, :string
|
||||
attr_json :md5_full, :string
|
||||
attr_json :md5s, ActiveModel::Type::Value.new
|
||||
attr_json :file_order, :integer
|
||||
end
|
||||
@@ -5,6 +5,31 @@ class Domain::User < ReduxApplicationRecord
|
||||
self.table_name = "domain_users"
|
||||
abstract!
|
||||
|
||||
class_attribute :class_has_created_posts,
|
||||
:class_has_faved_posts,
|
||||
:class_has_followed_users,
|
||||
:class_has_followed_by_users
|
||||
|
||||
sig(:final) { returns(T::Boolean) }
|
||||
def has_created_posts?
|
||||
class_has_created_posts.present?
|
||||
end
|
||||
|
||||
sig(:final) { returns(T::Boolean) }
|
||||
def has_faved_posts?
|
||||
class_has_faved_posts.present?
|
||||
end
|
||||
|
||||
sig(:final) { returns(T::Boolean) }
|
||||
def has_followed_users?
|
||||
class_has_followed_users.present?
|
||||
end
|
||||
|
||||
sig(:final) { returns(T::Boolean) }
|
||||
def has_followed_by_users?
|
||||
class_has_followed_by_users.present?
|
||||
end
|
||||
|
||||
sig { returns(String) }
|
||||
def type
|
||||
super
|
||||
@@ -50,6 +75,7 @@ class Domain::User < ReduxApplicationRecord
|
||||
|
||||
sig { params(klass: T.class_of(Domain::Post)).void }
|
||||
def self.has_created_posts!(klass)
|
||||
self.class_has_created_posts = klass
|
||||
has_many :posts,
|
||||
through: :user_post_creations,
|
||||
source: :post,
|
||||
@@ -58,6 +84,7 @@ class Domain::User < ReduxApplicationRecord
|
||||
|
||||
sig { params(klass: T.class_of(Domain::Post)).void }
|
||||
def self.has_faved_posts!(klass)
|
||||
self.class_has_faved_posts = klass
|
||||
has_many :faved_posts,
|
||||
through: :user_post_favs,
|
||||
source: :post,
|
||||
@@ -66,6 +93,7 @@ class Domain::User < ReduxApplicationRecord
|
||||
|
||||
sig { params(klass: T.class_of(Domain::User)).void }
|
||||
def self.has_followed_users!(klass)
|
||||
self.class_has_followed_users = klass
|
||||
has_many :followed_users,
|
||||
through: :user_user_follows_from,
|
||||
source: :to,
|
||||
@@ -74,6 +102,7 @@ class Domain::User < ReduxApplicationRecord
|
||||
|
||||
sig { params(klass: T.class_of(Domain::User)).void }
|
||||
def self.has_followed_by_users!(klass)
|
||||
self.class_has_followed_by_users = klass
|
||||
has_many :followed_by_users,
|
||||
through: :user_user_follows_to,
|
||||
source: :from,
|
||||
@@ -89,4 +118,20 @@ class Domain::User < ReduxApplicationRecord
|
||||
class_name: "::Domain::UserAvatar",
|
||||
inverse_of: :user,
|
||||
dependent: :destroy
|
||||
|
||||
sig { abstract.returns(String) }
|
||||
def account_status_for_view
|
||||
end
|
||||
|
||||
sig { abstract.returns(T.nilable(ActiveSupport::TimeWithZone)) }
|
||||
def registered_at_for_view
|
||||
end
|
||||
|
||||
sig { abstract.returns(T.nilable(String)) }
|
||||
def external_profile_url_for_view
|
||||
end
|
||||
|
||||
sig { abstract.returns(T.nilable(String)) }
|
||||
def name_for_view
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,6 +6,7 @@ class Domain::User::E621User < Domain::User
|
||||
attr_json :num_other_favs_cached, :integer
|
||||
attr_json :scanned_favs_status, :string
|
||||
attr_json :scanned_favs_at, :datetime
|
||||
attr_json :registered_at, :datetime
|
||||
|
||||
has_many :uploaded_posts,
|
||||
class_name: "::Domain::Post::E621Post",
|
||||
@@ -26,4 +27,24 @@ class Domain::User::E621User < Domain::User
|
||||
def to_param
|
||||
"e621/#{e621_id}" if e621_id.present?
|
||||
end
|
||||
|
||||
sig { override.returns(String) }
|
||||
def account_status_for_view
|
||||
"ok"
|
||||
end
|
||||
|
||||
sig { override.returns(T.nilable(ActiveSupport::TimeWithZone)) }
|
||||
def registered_at_for_view
|
||||
registered_at
|
||||
end
|
||||
|
||||
sig { override.returns(T.nilable(String)) }
|
||||
def external_profile_url_for_view
|
||||
"https://e621.net/users/#{e621_id}" if e621_id.present?
|
||||
end
|
||||
|
||||
sig { override.returns(T.nilable(String)) }
|
||||
def name_for_view
|
||||
name
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,6 +4,7 @@ class Domain::User::FaUser < Domain::User
|
||||
attr_json :name, :string
|
||||
attr_json :url_name, :string
|
||||
attr_json :full_name, :string
|
||||
attr_json :account_status, :string
|
||||
attr_json :artist_type, :string
|
||||
attr_json :mood, :string
|
||||
attr_json :profile_html, :string
|
||||
@@ -42,7 +43,17 @@ class Domain::User::FaUser < Domain::User
|
||||
|
||||
validates :name, presence: true
|
||||
validates :url_name, presence: true
|
||||
validates :state, presence: true, inclusion: { in: %w[ok error] }
|
||||
validates :state,
|
||||
presence: true,
|
||||
inclusion: {
|
||||
in: %w[ok account_disabled error],
|
||||
}
|
||||
|
||||
validates :account_status,
|
||||
inclusion: {
|
||||
in: %w[ok account_disabled error],
|
||||
allow_nil: true,
|
||||
}
|
||||
|
||||
after_initialize { self.state ||= "ok" if new_record? }
|
||||
|
||||
@@ -91,6 +102,35 @@ class Domain::User::FaUser < Domain::User
|
||||
end
|
||||
end
|
||||
|
||||
# TODO - write a test for this
|
||||
sig { override.returns(String) }
|
||||
def account_status_for_view
|
||||
account_status ||
|
||||
begin
|
||||
if (hle = last_user_page_log_entry) && (response = hle.response) &&
|
||||
(contents = response.contents)
|
||||
parser =
|
||||
Domain::Fa::Parser::Page.new(contents, require_logged_in: false)
|
||||
parser.user_page.account_status if parser.probably_user_page?
|
||||
end
|
||||
end || "unknown"
|
||||
end
|
||||
|
||||
sig { override.returns(T.nilable(String)) }
|
||||
def external_profile_url_for_view
|
||||
"https://www.furaffinity.net/user/#{url_name}" if url_name.present?
|
||||
end
|
||||
|
||||
sig { override.returns(T.nilable(ActiveSupport::TimeWithZone)) }
|
||||
def registered_at_for_view
|
||||
registered_at
|
||||
end
|
||||
|
||||
sig { override.returns(T.nilable(String)) }
|
||||
def name_for_view
|
||||
url_name
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
sig do
|
||||
|
||||
@@ -20,6 +20,9 @@ class Domain::User::InkbunnyUser < Domain::User
|
||||
class_name: "::HttpLogEntry",
|
||||
optional: true
|
||||
|
||||
has_created_posts! Domain::Post::InkbunnyPost
|
||||
has_faved_posts! Domain::Post::InkbunnyPost
|
||||
|
||||
validates :ib_id, presence: true
|
||||
validates :name, presence: true
|
||||
validates :state, presence: true, inclusion: { in: %w[ok error] }
|
||||
@@ -28,4 +31,25 @@ class Domain::User::InkbunnyUser < Domain::User
|
||||
def to_param
|
||||
"ib/#{name}" if name.present?
|
||||
end
|
||||
|
||||
sig { override.returns(String) }
|
||||
def account_status_for_view
|
||||
"ok"
|
||||
end
|
||||
|
||||
# TODO - can we get this from the API? or scrape the user page?
|
||||
sig { override.returns(T.nilable(ActiveSupport::TimeWithZone)) }
|
||||
def registered_at_for_view
|
||||
nil
|
||||
end
|
||||
|
||||
sig { override.returns(T.nilable(String)) }
|
||||
def external_profile_url_for_view
|
||||
"https://inkbunny.net/user/#{name}" if name.present?
|
||||
end
|
||||
|
||||
sig { override.returns(T.nilable(String)) }
|
||||
def name_for_view
|
||||
name
|
||||
end
|
||||
end
|
||||
|
||||
3
app/policies/domain/post/e621_post_policy.rb
Normal file
3
app/policies/domain/post/e621_post_policy.rb
Normal file
@@ -0,0 +1,3 @@
|
||||
# typed: strict
|
||||
class Domain::Post::E621PostPolicy < Domain::PostPolicy
|
||||
end
|
||||
3
app/policies/domain/post/fa_post_policy.rb
Normal file
3
app/policies/domain/post/fa_post_policy.rb
Normal file
@@ -0,0 +1,3 @@
|
||||
# typed: strict
|
||||
class Domain::Post::FaPostPolicy < Domain::PostPolicy
|
||||
end
|
||||
3
app/policies/domain/post/inkbunny_post_policy.rb
Normal file
3
app/policies/domain/post/inkbunny_post_policy.rb
Normal file
@@ -0,0 +1,3 @@
|
||||
# typed: strict
|
||||
class Domain::Post::InkbunnyPostPolicy < Domain::PostPolicy
|
||||
end
|
||||
19
app/policies/domain/post_policy.rb
Normal file
19
app/policies/domain/post_policy.rb
Normal file
@@ -0,0 +1,19 @@
|
||||
class Domain::PostPolicy < ApplicationPolicy
|
||||
def show?
|
||||
true
|
||||
end
|
||||
|
||||
def index?
|
||||
true
|
||||
end
|
||||
|
||||
def view_file?
|
||||
user&.admin?
|
||||
end
|
||||
|
||||
class Scope < ApplicationPolicy::Scope
|
||||
def resolve
|
||||
scope.all
|
||||
end
|
||||
end
|
||||
end
|
||||
3
app/policies/domain/user/e621_user_policy.rb
Normal file
3
app/policies/domain/user/e621_user_policy.rb
Normal file
@@ -0,0 +1,3 @@
|
||||
# typed: strict
|
||||
class Domain::User::E621UserPolicy < Domain::UserPolicy
|
||||
end
|
||||
3
app/policies/domain/user/fa_user_policy.rb
Normal file
3
app/policies/domain/user/fa_user_policy.rb
Normal file
@@ -0,0 +1,3 @@
|
||||
# typed: strict
|
||||
class Domain::User::FaUserPolicy < Domain::UserPolicy
|
||||
end
|
||||
3
app/policies/domain/user/inkbunny_user_policy.rb
Normal file
3
app/policies/domain/user/inkbunny_user_policy.rb
Normal file
@@ -0,0 +1,3 @@
|
||||
# typed: strict
|
||||
class Domain::User::InkbunnyUserPolicy < Domain::UserPolicy
|
||||
end
|
||||
14
app/policies/domain/user_policy.rb
Normal file
14
app/policies/domain/user_policy.rb
Normal file
@@ -0,0 +1,14 @@
|
||||
# typed: strict
|
||||
class Domain::UserPolicy < ApplicationPolicy
|
||||
extend T::Sig
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def show?
|
||||
true
|
||||
end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def view_page_scanned_at_timestamps?
|
||||
user&.admin?
|
||||
end
|
||||
end
|
||||
@@ -1,7 +1,4 @@
|
||||
<div
|
||||
id="<%= dom_id post %>"
|
||||
class="mx-auto mt-4 flex w-full flex-col gap-4 pb-4 md:max-w-2xl"
|
||||
>
|
||||
<div class="mx-auto mt-4 flex w-full flex-col gap-4 pb-4 md:max-w-2xl">
|
||||
<section class="border border-slate-300 bg-slate-50 p-4 md:rounded-md">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
<div
|
||||
id="<%= dom_id post %>"
|
||||
class="mx-auto mt-4 flex w-full max-w-2xl flex-col gap-4 pb-4"
|
||||
>
|
||||
<div class="mx-auto mt-4 flex w-full max-w-2xl flex-col gap-4 pb-4">
|
||||
<section class="rounded-md border border-slate-300 bg-slate-50 p-4">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div class="flex min-w-0 items-center gap-4">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div id="<%= dom_id user %>" class="mx-auto my-4 w-full space-y-4 md:max-w-2xl">
|
||||
<div class="mx-auto my-4 w-full space-y-4 md:max-w-2xl">
|
||||
<%= render "domain/fa/users/show_sections/name_icon_and_status", user: user %>
|
||||
<div class="flex flex-col gap-4 sm:flex-row">
|
||||
<div class="w-full sm:w-1/2">
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
<div
|
||||
id="<%= dom_id @post %>"
|
||||
class="mx-auto mt-4 flex w-full max-w-2xl flex-col gap-4 pb-4"
|
||||
>
|
||||
<div class="mx-auto mt-4 flex w-full max-w-2xl flex-col gap-4 pb-4">
|
||||
<section class="rounded-md border border-slate-300 bg-slate-50 p-4">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div class="flex min-w-0 items-center gap-4">
|
||||
|
||||
53
app/views/domain/posts/_as_gallery_item.html.erb
Normal file
53
app/views/domain/posts/_as_gallery_item.html.erb
Normal file
@@ -0,0 +1,53 @@
|
||||
<div
|
||||
class="m-4 flex h-fit flex-col rounded-lg border border-slate-300 bg-slate-50 shadow-sm"
|
||||
>
|
||||
<div class="flex justify-between border-b border-slate-300 p-4">
|
||||
<div>
|
||||
<%= render partial: "inline_postable_domain_link", locals: { post: post } %>
|
||||
</div>
|
||||
<div>
|
||||
<% if creator = post.primary_creator_for_view %>
|
||||
<%= link_to creator.name_for_view,
|
||||
domain_user_path(creator),
|
||||
class: "text-blue-600 hover:text-blue-800" %>
|
||||
<% elsif fallback = post.primary_creator_name_fallback_for_view %>
|
||||
<%= fallback %>
|
||||
<% else %>
|
||||
(nobody)
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-center p-4">
|
||||
<% if primary_file =
|
||||
post.primary_file_for_view && primary_file&.blob_sha256.present? %>
|
||||
<%= link_to domain_post_path(post) do %>
|
||||
<%= image_tag blob_path(
|
||||
HexUtil.bin2hex(primary_file.blob_sha256),
|
||||
format: "jpg",
|
||||
thumb: "small",
|
||||
),
|
||||
class:
|
||||
"max-h-[300px] max-w-[300px] rounded-md border border-slate-300 object-contain shadow-md",
|
||||
alt: post.title %>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<span>No file available</span>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="border-t border-slate-300">
|
||||
<h2 class="p-4 text-center text-lg">
|
||||
<%= link_to post.title, domain_post_path(post), class: "sky-link" %>
|
||||
</h2>
|
||||
<div class="px-4 pb-4 text-sm text-slate-500">
|
||||
<div class="flex justify-end">
|
||||
<% if post.posted_at %>
|
||||
Posted <%= time_ago_in_words(post.posted_at) %> ago
|
||||
<% else %>
|
||||
Post date unknown
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
0
app/views/domain/posts/_as_table_row_item.html.erb
Normal file
0
app/views/domain/posts/_as_table_row_item.html.erb
Normal file
25
app/views/domain/posts/_inline_postable_domain_link.html.erb
Normal file
25
app/views/domain/posts/_inline_postable_domain_link.html.erb
Normal file
@@ -0,0 +1,25 @@
|
||||
<div class="flex w-full items-center justify-between">
|
||||
<%= image_tag domain_post_domain_icon_path(post),
|
||||
class: "w-6 h-6",
|
||||
title: domain_post_domain_icon_title(post) %>
|
||||
<% link_class =
|
||||
"flex items-center text-slate-500 hover:text-slate-700 decoration-dotted underline" %>
|
||||
<% url = post.domain_url_for_view %>
|
||||
<% if url.present? %>
|
||||
<%= link_to url.to_s, target: "_blank", rel: "noopener", class: link_class do %>
|
||||
<span
|
||||
><%= post.domain_abbreviation_for_view %>
|
||||
#<%= post.domain_id_for_view %></span
|
||||
>
|
||||
<%= render partial: "shared/icons/external_link",
|
||||
locals: {
|
||||
class_name: "w-4 h-4 ml-1",
|
||||
} %>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<span
|
||||
><%= post.domain_abbreviation_for_view %>
|
||||
#<%= post.domain_id_for_view %></span
|
||||
>
|
||||
<% end %>
|
||||
</div>
|
||||
105
app/views/domain/posts/index.html.erb
Normal file
105
app/views/domain/posts/index.html.erb
Normal file
@@ -0,0 +1,105 @@
|
||||
<% content_for :head do %>
|
||||
<style>
|
||||
.grid-cell {
|
||||
padding: 0.25rem;
|
||||
border-right: 1px solid #e2e8f0;
|
||||
}
|
||||
.grid-cell:last-child {
|
||||
padding-left: 0;
|
||||
padding-right: 1rem;
|
||||
border-right: none;
|
||||
}
|
||||
.grid-cell:first-child {
|
||||
padding-left: 1rem;
|
||||
}
|
||||
.grid-row:hover .grid-cell {
|
||||
background-color: #f1f5f9;
|
||||
}
|
||||
</style>
|
||||
<% end %>
|
||||
|
||||
<div class="mx-auto mt-4 text-center sm:mt-6">
|
||||
<h1 class="text-2xl">All Posts <%= page_str(params) %></h1>
|
||||
</div>
|
||||
|
||||
<div class="mb-6 bg-white shadow">
|
||||
<div class="mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div
|
||||
class="flex flex-col items-center justify-between gap-4 py-4 sm:flex-row"
|
||||
>
|
||||
<!-- Domain Filters -->
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<span class="my-auto font-medium text-gray-700">Sources:</span>
|
||||
<% active_sources = (params[:sources] || SourceHelper.all_source_names).uniq %>
|
||||
<% SourceHelper.all_source_names.each do |source| %>
|
||||
<% is_active = active_sources.include?(source) %>
|
||||
<% link_sources =
|
||||
(
|
||||
if is_active
|
||||
active_sources - [source]
|
||||
else
|
||||
active_sources + [source]
|
||||
end
|
||||
) %>
|
||||
<% posts_path_url =
|
||||
if SourceHelper.has_all_sources?(link_sources)
|
||||
domain_posts_path(view: params[:view])
|
||||
else
|
||||
domain_posts_path(sources: link_sources, view: params[:view])
|
||||
end %>
|
||||
<%= link_to(
|
||||
source.titleize,
|
||||
posts_path_url,
|
||||
class:
|
||||
"px-3 py-1 rounded-full text-sm #{is_active ? "bg-blue-500 text-white" : "bg-gray-100 text-gray-700 hover:bg-gray-200"}",
|
||||
) %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<!-- View Type Selector -->
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-medium text-gray-700">View:</span>
|
||||
<%= link_to(
|
||||
domain_posts_path(view: "gallery", sources: params[:sources]),
|
||||
class:
|
||||
"px-3 py-1 rounded-full text-sm #{params[:view] != "table" ? "bg-blue-500 text-white" : "bg-gray-100 text-gray-700 hover:bg-gray-200"}",
|
||||
) do %>
|
||||
<i class="fas fa-th-large mr-1"></i> Gallery
|
||||
<% end %>
|
||||
<%= link_to(
|
||||
domain_posts_path(view: "table", sources: params[:sources]),
|
||||
class:
|
||||
"px-3 py-1 rounded-full text-sm #{params[:view] == "table" ? "bg-blue-500 text-white" : "bg-gray-100 text-gray-700 hover:bg-gray-200"}",
|
||||
) do %>
|
||||
<i class="fas fa-list mr-1"></i> Table
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<%= render partial: "shared/pagination_controls", locals: { collection: @posts } %>
|
||||
<% if params[:view] == "table" %>
|
||||
<div
|
||||
class="mx-auto grid grid-cols-[auto_1fr_auto_auto_auto] border-b border-slate-300 text-sm"
|
||||
>
|
||||
<div class="grid-row contents">
|
||||
<div class="grid-cell text-center font-semibold">Thumbnail</div>
|
||||
<div class="grid-cell text-left font-semibold">Title</div>
|
||||
<div class="grid-cell text-center font-semibold">Artist</div>
|
||||
<div class="grid-cell text-center font-semibold">Source</div>
|
||||
<div class="grid-cell text-right font-semibold">Posted</div>
|
||||
</div>
|
||||
<div class="col-span-full border-b border-slate-300"></div>
|
||||
|
||||
<% @posts.each do |post| %>
|
||||
<%= render partial: "as_table_row_item", locals: { post: post } %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="mx-auto flex flex-wrap justify-center">
|
||||
<% @posts.each do |post| %>
|
||||
<%= render partial: "as_gallery_item", locals: { post: post } %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<%= render partial: "shared/pagination_controls", locals: { collection: @posts } %>
|
||||
116
app/views/domain/posts/show.html.erb
Normal file
116
app/views/domain/posts/show.html.erb
Normal file
@@ -0,0 +1,116 @@
|
||||
<div class="mx-auto mt-4 flex w-full max-w-2xl flex-col gap-4 pb-4">
|
||||
<section class="rounded-md border border-slate-300 bg-slate-50 p-4">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div class="flex min-w-0 items-center gap-4">
|
||||
<div class="flex min-w-0 items-center gap-2">
|
||||
<span class="truncate text-lg font-medium">
|
||||
<%= link_to @post.title,
|
||||
@post.external_url_for_view,
|
||||
class: "text-blue-600 hover:underline",
|
||||
target: "_blank" %>
|
||||
</span>
|
||||
<i class="fa-solid fa-arrow-up-right-from-square text-slate-400"></i>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 whitespace-nowrap text-slate-600">
|
||||
by
|
||||
<%= render "domain/fa/users/inline_link",
|
||||
user: @post.creator,
|
||||
with_post_count: false %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2 flex flex-wrap gap-x-4 text-sm text-slate-600">
|
||||
<span>
|
||||
<i class="fa-regular fa-calendar mr-1"></i>
|
||||
Posted: <%= @post.posted_at&.strftime("%Y-%m-%d") %>
|
||||
(<%= time_ago_in_words(@post.posted_at) if @post.posted_at %> ago)
|
||||
</span>
|
||||
<span>
|
||||
<i class="fa-solid fa-eye mr-1"></i>
|
||||
Views: <%= @post.num_views %>
|
||||
</span>
|
||||
<span>
|
||||
<i class="fa-solid fa-comment mr-1"></i>
|
||||
Comments: <%= @post.num_comments %>
|
||||
</span>
|
||||
<span>
|
||||
<i class="fa-solid fa-heart mr-1"></i>
|
||||
Favorites: <%= @post.num_favorites %>
|
||||
<% if policy(@post).view_scraper_metadata? %>
|
||||
(<%= link_to pluralize(@post.faved_by.count, "fav"),
|
||||
favorites_domain_fa_post_path(@post),
|
||||
class: "text-blue-600 hover:underline" %>)
|
||||
<% end %>
|
||||
</span>
|
||||
</div>
|
||||
<% if policy(@post).view_scraper_metadata? %>
|
||||
<% scanned_at = @post.scanned_at %>
|
||||
<% scanned_hle = @post.last_submission_page || guess_scanned_http_log_entry(@post) %>
|
||||
<% scanned_at ||= scanned_hle&.requested_at %>
|
||||
<% if scanned_at %>
|
||||
<div class="mt-2 text-sm text-slate-500">
|
||||
<div class="mt-2 text-sm text-slate-500">
|
||||
<% if scanned_hle %>
|
||||
<%= link_to "Scanned #{time_ago_in_words(scanned_at)} ago",
|
||||
log_entry_path(scanned_hle),
|
||||
class: "text-blue-600 hover:underline",
|
||||
target: "_blank" %>
|
||||
<% else %>
|
||||
<span> Scanned <%= time_ago_in_words(scanned_at) %> ago </span>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="mt-2 text-sm text-slate-500">Unknown when post scanned</div>
|
||||
<% end %>
|
||||
<% if hle = guess_file_downloaded_http_log_entry(@post) %>
|
||||
<div class="mt-2 text-sm text-slate-500">
|
||||
<%= link_to "File downloaded #{time_ago_in_words(hle.requested_at)} ago",
|
||||
log_entry_path(hle),
|
||||
class: "text-blue-600 hover:underline",
|
||||
target: "_blank" %>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="mt-2 text-sm text-slate-500">
|
||||
Unknown when file downloaded
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</section>
|
||||
|
||||
<% if policy(@post).view_file? %>
|
||||
<section>
|
||||
<% if @post.file %>
|
||||
<%= render partial: "log_entries/content_container",
|
||||
locals: {
|
||||
log_entry: @post.file,
|
||||
} %>
|
||||
<% else %>
|
||||
<% if !@post.scanned? %>
|
||||
<%= button_to "Force scan post", scan_post_domain_fa_post_path(fa_id: @post.fa_id) %>
|
||||
<% elsif !@post.file %>
|
||||
<%= button_to "Force scan file", scan_post_domain_fa_post_path(fa_id: @post.fa_id) %>
|
||||
<% else %>
|
||||
Scanned and have file
|
||||
<% end %>
|
||||
<% end %>
|
||||
</section>
|
||||
|
||||
<%= render partial: "log_entries/file_details_sky_section",
|
||||
locals: {
|
||||
log_entry: @post.file,
|
||||
} %>
|
||||
<% else %>
|
||||
<section class="sky-section">
|
||||
<%= link_to "https://www.furaffinity.net/view/#{@post.fa_id}/",
|
||||
target: "_blank",
|
||||
rel: "noopener noreferrer",
|
||||
class: "section-header flex items-center gap-2 hover:text-slate-600" do %>
|
||||
<span>View Post on FurAffinity</span>
|
||||
<i class="fa-solid fa-arrow-up-right-from-square"></i>
|
||||
<% end %>
|
||||
</section>
|
||||
<% end %>
|
||||
<%= render "section_description", { post: @post } %>
|
||||
<%= render "section_similar_posts", { post: @post } %>
|
||||
</div>
|
||||
13
app/views/domain/users/show.html.erb
Normal file
13
app/views/domain/users/show.html.erb
Normal file
@@ -0,0 +1,13 @@
|
||||
<div class="mx-auto my-4 w-full space-y-4 md:max-w-2xl">
|
||||
<%= render "domain/users/show_sections/name_icon_and_status", user: @user %>
|
||||
<div class="flex flex-col gap-4 sm:flex-row">
|
||||
<div class="w-full sm:w-1/2">
|
||||
<%= render "domain/users/show_sections/stats", user: @user %>
|
||||
</div>
|
||||
<div class="w-full sm:w-1/2">
|
||||
<%= render "domain/users/show_sections/recent_created_posts", user: @user %>
|
||||
</div>
|
||||
</div>
|
||||
<%= render "domain/users/show_sections/profile_description", user: @user %>
|
||||
<%= render "domain/users/show_sections/similar_users", user: @user %>
|
||||
</div>
|
||||
@@ -0,0 +1,47 @@
|
||||
<section class="animated-shadow-sky sky-section flex divide-none p-3">
|
||||
<div class="flex grow items-center gap-4">
|
||||
<img
|
||||
src="<%= domain_user_avatar_img_src_path(user.avatar) %>"
|
||||
class="h-12 w-12 rounded-lg"
|
||||
/>
|
||||
<div>
|
||||
<div class="text-lg font-bold text-slate-900">
|
||||
<%= user.name_for_view %>
|
||||
</div>
|
||||
<div class="flex gap-6 text-sm text-slate-400">
|
||||
<% if policy(user).view_page_scanned_at_timestamps? %>
|
||||
<div class="flex flex-col">
|
||||
<span class="font-medium italic text-slate-500">Status</span>
|
||||
<span class=""><%= user.account_status_for_view %></span>
|
||||
</div>
|
||||
<% if user.respond_to?(:state) %>
|
||||
<div class="flex flex-col">
|
||||
<span class="font-medium italic text-slate-500">State</span>
|
||||
<span class=""><%= user.state %></span>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<div class="flex flex-col">
|
||||
<span class="font-medium italic text-slate-500">Registered</span>
|
||||
<span class="">
|
||||
<% if registered_at = user.registered_at_for_view %>
|
||||
<%= time_ago_in_words(registered_at) %>
|
||||
ago
|
||||
<% else %>
|
||||
unknown
|
||||
<% end %>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a
|
||||
href="<%= user.external_profile_url_for_view %>"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="sky-link flex items-center gap-2"
|
||||
>
|
||||
<span class="font-bold"><%= site_name_for_user(user) %></span>
|
||||
<img src="<%= site_icon_path_for_user(user) %>" class="h-5 w-5" />
|
||||
</a>
|
||||
</section>
|
||||
@@ -0,0 +1,3 @@
|
||||
<% if user.is_a?(Domain::User::FaUser) %>
|
||||
<%= render "domain/users/show_sections/profile_description_fa", user: user %>
|
||||
<% end %>
|
||||
@@ -0,0 +1,12 @@
|
||||
<section class="animated-shadow-sky sky-section">
|
||||
<% if (profile_html = user.profile_html) %>
|
||||
<h2 class="section-header">Profile Description</h2>
|
||||
<div class="bg-slate-800 p-4 text-slate-200">
|
||||
<% cache(user, expires_in: 12.hours) do %>
|
||||
<%= sanitized_user_profile_html(profile_html) %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="px-4 py-3 text-slate-500">No profile description available</div>
|
||||
<% end %>
|
||||
</section>
|
||||
@@ -0,0 +1,30 @@
|
||||
<% if user.has_created_posts? %>
|
||||
<section class="animated-shadow-sky sky-section">
|
||||
<h2 class="section-header">
|
||||
<span class="font-medium text-slate-900">Recent Posts</span>
|
||||
<span class="float-right">
|
||||
<%= link_to "#{user.posts.count} total",
|
||||
domain_user_posts_path(user),
|
||||
class: "sky-link" %>
|
||||
</span>
|
||||
</h2>
|
||||
<% if user.posts.any? %>
|
||||
<% user
|
||||
.posts
|
||||
.order(fa_id: :desc)
|
||||
.limit(5)
|
||||
.each do |post| %>
|
||||
<div class="flex items-center px-4 py-2">
|
||||
<span class="grow truncate">
|
||||
<%= link_to post.title, domain_post_path(post), class: "sky-link block truncate" %>
|
||||
</span>
|
||||
<span class="whitespace-nowrap text-slate-500">
|
||||
<%= time_ago_in_words(post.created_at) %> ago
|
||||
</span>
|
||||
</div>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<div class="px-4 py-3 text-slate-500">No posts found</div>
|
||||
<% end %>
|
||||
</section>
|
||||
<% end %>
|
||||
11
app/views/domain/users/show_sections/_stats.html.erb
Normal file
11
app/views/domain/users/show_sections/_stats.html.erb
Normal file
@@ -0,0 +1,11 @@
|
||||
<section class="sky-section animated-shadow-sky divide-y">
|
||||
<h2 class="section-header">User Stats</h2>
|
||||
<% stat_rows_for_user(user).each do |value_label, value| %>
|
||||
<div class="flex items-center px-4 py-2">
|
||||
<span class="grow text-slate-900"><%= value_label %></span>
|
||||
<span class="text-slate-500">
|
||||
<%= value.is_a?(Integer) ? number_with_delimiter(value, delimiter: ",") : value %>
|
||||
</span>
|
||||
</div>
|
||||
<% end %>
|
||||
</section>
|
||||
@@ -18,6 +18,22 @@ Rails.application.routes.draw do
|
||||
end
|
||||
end
|
||||
|
||||
resources :domain_users,
|
||||
only: [:show],
|
||||
constraints: {
|
||||
id: %r{[^/]+/[^/]+},
|
||||
},
|
||||
controller: "domain/users" do
|
||||
resources :posts, only: [:index]
|
||||
end
|
||||
|
||||
resources :domain_posts,
|
||||
only: %i[show index],
|
||||
constraints: {
|
||||
id: %r{[^/]+/[^/]+},
|
||||
},
|
||||
controller: "domain/posts"
|
||||
|
||||
namespace :domain do
|
||||
namespace :fa do
|
||||
resources :users,
|
||||
|
||||
@@ -8,6 +8,7 @@ class CreateUnifiedDomainTables < ActiveRecord::Migration[7.2]
|
||||
Domain::Post::E621Post
|
||||
Domain::Post::InkbunnyPost
|
||||
Domain::Post::SofurryPost
|
||||
Domain::Post::WeasylPost
|
||||
]
|
||||
|
||||
USER_TYPES = %w[
|
||||
@@ -15,19 +16,37 @@ class CreateUnifiedDomainTables < ActiveRecord::Migration[7.2]
|
||||
Domain::User::E621User
|
||||
Domain::User::InkbunnyUser
|
||||
Domain::User::SofurryUser
|
||||
Domain::User::WeasylUser
|
||||
]
|
||||
|
||||
POST_FILE_TYPES = %w[Domain::PostFile Domain::PostFile::InkbunnyPostFile]
|
||||
|
||||
GROUP_JOIN_TYPES = %w[
|
||||
Domain::PostGroupJoin::InkbunnyPoolJoin
|
||||
Domain::PostGroupJoin::E621PoolJoin
|
||||
]
|
||||
|
||||
GROUP_TYPES = %w[Domain::PostGroup::InkbunnyPool Domain::PostGroup::E621Pool]
|
||||
|
||||
sig { params(name: String, values: T::Array[String]).void }
|
||||
def create_enum(name, values)
|
||||
reversible do |dir|
|
||||
dir.up do
|
||||
execute "CREATE TYPE #{name} AS ENUM (#{values.map { |t| "'#{t}'" }.join(",")})"
|
||||
end
|
||||
|
||||
dir.down { execute "DROP TYPE #{name}" }
|
||||
end
|
||||
end
|
||||
|
||||
sig { void }
|
||||
def change
|
||||
up_only { execute "SET DEFAULT_TABLESPACE = mirai" }
|
||||
|
||||
reversible do |dir|
|
||||
dir.up do
|
||||
execute "CREATE TYPE domain_post_type AS ENUM (#{POST_TYPES.map { |t| "'#{t}'" }.join(", ")})"
|
||||
end
|
||||
|
||||
dir.down { execute "DROP TYPE domain_post_type" }
|
||||
end
|
||||
create_enum("domain_post_type", POST_TYPES)
|
||||
create_enum("domain_user_type", USER_TYPES)
|
||||
create_enum("domain_post_file_type", POST_FILE_TYPES)
|
||||
create_enum("domain_post_group_type", GROUP_TYPES)
|
||||
create_enum("domain_post_group_join_type", GROUP_JOIN_TYPES)
|
||||
|
||||
create_table :domain_posts do |t|
|
||||
t.enum :type, null: false, enum_type: "domain_post_type"
|
||||
@@ -37,14 +56,6 @@ class CreateUnifiedDomainTables < ActiveRecord::Migration[7.2]
|
||||
t.index :type
|
||||
end
|
||||
|
||||
reversible do |dir|
|
||||
dir.up do
|
||||
execute "CREATE TYPE domain_user_type AS ENUM (#{USER_TYPES.map { |t| "'#{t}'" }.join(", ")})"
|
||||
end
|
||||
|
||||
dir.down { execute "DROP TYPE domain_user_type" }
|
||||
end
|
||||
|
||||
create_table :domain_users do |t|
|
||||
t.enum :type, null: false, enum_type: "domain_user_type"
|
||||
t.jsonb :json_attributes, default: {}
|
||||
@@ -54,25 +65,26 @@ class CreateUnifiedDomainTables < ActiveRecord::Migration[7.2]
|
||||
end
|
||||
|
||||
create_table :domain_user_avatars do |t|
|
||||
t.string :type, null: false
|
||||
t.references :user, null: false, foreign_key: { to_table: :domain_users }
|
||||
t.jsonb :json_attributes, default: {}
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
create_table :domain_post_files do |t|
|
||||
t.enum :type, null: false, enum_type: "domain_post_file_type"
|
||||
t.references :post, null: false, foreign_key: { to_table: :domain_posts }
|
||||
t.references :log_entry,
|
||||
null: true,
|
||||
foreign_key: {
|
||||
to_table: :http_log_entries,
|
||||
}
|
||||
t.binary :blob_sha256, null: true
|
||||
t.jsonb :json_attributes, default: {}
|
||||
t.timestamps
|
||||
|
||||
t.index :type
|
||||
end
|
||||
|
||||
create_table :domain_post_files do |t|
|
||||
t.references :post, null: false, foreign_key: { to_table: :domain_posts }
|
||||
t.references :log_entry,
|
||||
null: false,
|
||||
foreign_key: {
|
||||
to_table: :http_log_entries,
|
||||
}
|
||||
t.jsonb :json_attributes, default: {}
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
create_table :domain_user_post_creations, id: false do |t|
|
||||
t.references :user,
|
||||
null: false,
|
||||
@@ -128,52 +140,39 @@ class CreateUnifiedDomainTables < ActiveRecord::Migration[7.2]
|
||||
end
|
||||
|
||||
reversible do |dir|
|
||||
dir.up do
|
||||
# timestamps are formatted like `2001-02-03T04:05:06Z`
|
||||
# aka ISO 8601
|
||||
execute <<~SQL
|
||||
CREATE OR REPLACE FUNCTION f_cast_isots(text)
|
||||
RETURNS timestamptz AS
|
||||
$$SELECT to_timestamp($1, 'YYYY-MM-DDTHH24:MI:SSZ')$$
|
||||
LANGUAGE sql IMMUTABLE;
|
||||
SQL
|
||||
end
|
||||
dir.up {}
|
||||
|
||||
dir.down { execute "DROP FUNCTION f_cast_isots(text)" }
|
||||
dir.down {}
|
||||
end
|
||||
|
||||
add_index :domain_posts,
|
||||
"(cast(json_attributes->>'fa_id' as integer))",
|
||||
where: "type = 'Domain::Post::FaPost'",
|
||||
name: "idx_domain_fa_posts_on_fa_id",
|
||||
unique: true
|
||||
create_table :domain_post_groups do |t|
|
||||
t.enum :type, null: false, enum_type: "domain_post_group_type"
|
||||
t.jsonb :json_attributes, default: {}
|
||||
t.timestamps
|
||||
|
||||
add_index :domain_posts,
|
||||
"((json_attributes->>'e621_id')::integer)",
|
||||
where: "type = 'Domain::Post::E621Post'",
|
||||
name: "idx_domain_e621_posts_on_e621_id",
|
||||
unique: true
|
||||
t.index :type
|
||||
end
|
||||
|
||||
add_index :domain_posts,
|
||||
"((json_attributes->>'uploader_user_id')::integer)",
|
||||
where: "type = 'Domain::Post::E621Post'",
|
||||
name: "idx_domain_e621_posts_on_uploader_user_id",
|
||||
unique: true
|
||||
create_table :domain_post_group_joins, id: false do |t|
|
||||
t.enum :type, null: false, enum_type: "domain_post_group_join_type"
|
||||
t.references :post,
|
||||
index: false,
|
||||
null: false,
|
||||
foreign_key: {
|
||||
to_table: :domain_posts,
|
||||
}
|
||||
t.references :group,
|
||||
index: false,
|
||||
null: false,
|
||||
foreign_key: {
|
||||
to_table: :domain_post_groups,
|
||||
}
|
||||
t.jsonb :json_attributes, default: {}
|
||||
t.timestamps
|
||||
|
||||
add_index :domain_users,
|
||||
"((json_attributes->>'url_name')::text)",
|
||||
where: "type = 'Domain::User::FaUser'",
|
||||
name: "idx_domain_fa_users_on_url_name",
|
||||
unique: true
|
||||
|
||||
add_index :domain_users,
|
||||
"((json_attributes->>'e621_id')::integer)",
|
||||
where: "type = 'Domain::User::E621User'",
|
||||
name: "idx_domain_e621_users_on_e621_id",
|
||||
unique: true
|
||||
|
||||
add_index :domain_users,
|
||||
"(json_attributes->>'migrated_user_favs_at')",
|
||||
name: "idx_domain_users_on_migrated_user_favs_at"
|
||||
t.index %i[post_id group_id], unique: true
|
||||
t.index %i[group_id post_id]
|
||||
t.index :type
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
# typed: strict
|
||||
class CreateUnifiedPostGroupTables < ActiveRecord::Migration[7.2]
|
||||
extend T::Sig
|
||||
GROUP_JOIN_TYPES = %w[
|
||||
Domain::PostGroupJoin::InkbunnyPoolJoin
|
||||
Domain::PostGroupJoin::E621PoolJoin
|
||||
]
|
||||
|
||||
GROUP_TYPES = %w[Domain::PostGroup::InkbunnyPool Domain::PostGroup::E621Pool]
|
||||
|
||||
sig { void }
|
||||
def change
|
||||
up_only { execute "SET DEFAULT_TABLESPACE = mirai" }
|
||||
|
||||
reversible do |dir|
|
||||
dir.up do
|
||||
execute "CREATE TYPE domain_post_group_type AS ENUM (#{GROUP_TYPES.map { |t| "'#{t}'" }.join(",")})"
|
||||
end
|
||||
|
||||
dir.down { execute "DROP TYPE domain_post_group_type" }
|
||||
end
|
||||
|
||||
reversible do |dir|
|
||||
dir.up do
|
||||
execute "CREATE TYPE domain_post_group_join_type AS ENUM (#{GROUP_JOIN_TYPES.map { |t| "'#{t}'" }.join(",")})"
|
||||
end
|
||||
|
||||
dir.down { execute "DROP TYPE domain_post_group_join_type" }
|
||||
end
|
||||
|
||||
# allow null for log_entry_id for files which have not yet been downloaded
|
||||
up_only do
|
||||
change_column :domain_post_files, :log_entry_id, :integer, null: true
|
||||
end
|
||||
|
||||
# avatars is not polymorphic, so we can remove the type column
|
||||
remove_column :domain_user_avatars, :type, :string, null: false
|
||||
|
||||
create_table :domain_post_groups do |t|
|
||||
t.enum :type, null: false, enum_type: "domain_post_group_type"
|
||||
t.jsonb :json_attributes, default: {}
|
||||
t.timestamps
|
||||
|
||||
t.index :type
|
||||
end
|
||||
|
||||
create_table :domain_post_group_joins, id: false do |t|
|
||||
t.enum :type, null: false, enum_type: "domain_post_group_join_type"
|
||||
t.references :post,
|
||||
index: false,
|
||||
null: false,
|
||||
foreign_key: {
|
||||
to_table: :domain_posts,
|
||||
}
|
||||
t.references :group,
|
||||
index: false,
|
||||
null: false,
|
||||
foreign_key: {
|
||||
to_table: :domain_post_groups,
|
||||
}
|
||||
t.jsonb :json_attributes, default: {}
|
||||
t.timestamps
|
||||
|
||||
t.index %i[post_id group_id], unique: true
|
||||
t.index %i[group_id post_id]
|
||||
t.index :type
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,71 @@
|
||||
class CreateUnifiedJsonAttributeIndexes < ActiveRecord::Migration[7.2]
|
||||
def change
|
||||
# Domain::Post::FaPost.fa_id
|
||||
add_index :domain_posts,
|
||||
"(cast(json_attributes->>'fa_id' as integer))",
|
||||
where: "type = 'Domain::Post::FaPost'",
|
||||
name: "idx_domain_fa_posts_on_fa_id",
|
||||
unique: true
|
||||
|
||||
# Domain::Post::E621Post.e621_id
|
||||
add_index :domain_posts,
|
||||
"((json_attributes->>'e621_id')::integer)",
|
||||
where: "type = 'Domain::Post::E621Post'",
|
||||
name: "idx_domain_e621_posts_on_e621_id",
|
||||
unique: true
|
||||
|
||||
# Domain::Post::InkbunnyPost.ib_id
|
||||
add_index :domain_posts,
|
||||
"((json_attributes->>'ib_id')::integer)",
|
||||
where: "type = 'Domain::Post::InkbunnyPost'",
|
||||
name: "idx_domain_inkbunny_posts_on_ib_id",
|
||||
unique: true
|
||||
|
||||
# Domain::Post::E621Post.uploader_user_id
|
||||
add_index :domain_posts,
|
||||
"((json_attributes->>'uploader_user_id')::integer)",
|
||||
where: "type = 'Domain::Post::E621Post'",
|
||||
name: "idx_domain_e621_posts_on_uploader_user_id",
|
||||
unique: true
|
||||
|
||||
# Domain::User::FaUser.url_name
|
||||
add_index :domain_users,
|
||||
"((json_attributes->>'url_name')::text)",
|
||||
where: "type = 'Domain::User::FaUser'",
|
||||
name: "idx_domain_fa_users_on_url_name",
|
||||
unique: true
|
||||
|
||||
# Domain::User::E621User.e621_id
|
||||
add_index :domain_users,
|
||||
"((json_attributes->>'e621_id')::integer)",
|
||||
where: "type = 'Domain::User::E621User'",
|
||||
name: "idx_domain_e621_users_on_e621_id",
|
||||
unique: true
|
||||
|
||||
# Domain::User::InkbunnyUser.ib_id
|
||||
add_index :domain_users,
|
||||
"((json_attributes->>'ib_id')::integer)",
|
||||
where: "type = 'Domain::User::InkbunnyUser'",
|
||||
name: "idx_domain_inkbunny_users_on_ib_id",
|
||||
unique: true
|
||||
|
||||
# Domain::User::InkbunnyUser.name
|
||||
add_index :domain_users,
|
||||
"((json_attributes->>'name')::text)",
|
||||
where: "type = 'Domain::User::InkbunnyUser'",
|
||||
name: "idx_domain_inkbunny_users_on_name",
|
||||
unique: true
|
||||
|
||||
# Domain::User.migrated_user_favs_at
|
||||
add_index :domain_users,
|
||||
"(json_attributes->>'migrated_user_favs_at')",
|
||||
name: "idx_domain_users_on_migrated_user_favs_at"
|
||||
|
||||
# Domain::PostFile::InkbunnyPostFile.ib_id
|
||||
add_index :domain_post_files,
|
||||
"((json_attributes->>'ib_id')::integer)",
|
||||
where: "type = 'Domain::PostFile::InkbunnyPostFile'",
|
||||
name: "idx_domain_inkbunny_post_files_on_ib_id",
|
||||
unique: true
|
||||
end
|
||||
end
|
||||
@@ -93,6 +93,16 @@ CREATE EXTENSION IF NOT EXISTS vector WITH SCHEMA public;
|
||||
COMMENT ON EXTENSION vector IS 'vector data type and ivfflat access method';
|
||||
|
||||
|
||||
--
|
||||
-- Name: domain_post_file_type; Type: TYPE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE TYPE public.domain_post_file_type AS ENUM (
|
||||
'Domain::PostFile',
|
||||
'Domain::PostFile::InkbunnyPostFile'
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: domain_post_group_join_type; Type: TYPE; Schema: public; Owner: -
|
||||
--
|
||||
@@ -121,7 +131,8 @@ CREATE TYPE public.domain_post_type AS ENUM (
|
||||
'Domain::Post::FaPost',
|
||||
'Domain::Post::E621Post',
|
||||
'Domain::Post::InkbunnyPost',
|
||||
'Domain::Post::SofurryPost'
|
||||
'Domain::Post::SofurryPost',
|
||||
'Domain::Post::WeasylPost'
|
||||
);
|
||||
|
||||
|
||||
@@ -133,7 +144,8 @@ CREATE TYPE public.domain_user_type AS ENUM (
|
||||
'Domain::User::FaUser',
|
||||
'Domain::User::E621User',
|
||||
'Domain::User::InkbunnyUser',
|
||||
'Domain::User::SofurryUser'
|
||||
'Domain::User::SofurryUser',
|
||||
'Domain::User::WeasylUser'
|
||||
);
|
||||
|
||||
|
||||
@@ -149,15 +161,6 @@ CREATE TYPE public.postable_type AS ENUM (
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: f_cast_isots(text); Type: FUNCTION; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE FUNCTION public.f_cast_isots(text) RETURNS timestamp with time zone
|
||||
LANGUAGE sql IMMUTABLE
|
||||
AS $_$SELECT to_timestamp($1, 'YYYY-MM-DDTHH24:MI:SSZ')$_$;
|
||||
|
||||
|
||||
SET default_tablespace = '';
|
||||
|
||||
SET default_table_access_method = heap;
|
||||
@@ -2687,8 +2690,10 @@ SET default_tablespace = mirai;
|
||||
|
||||
CREATE TABLE public.domain_post_files (
|
||||
id bigint NOT NULL,
|
||||
type public.domain_post_file_type NOT NULL,
|
||||
post_id bigint NOT NULL,
|
||||
log_entry_id integer,
|
||||
log_entry_id bigint,
|
||||
blob_sha256 bytea,
|
||||
json_attributes jsonb DEFAULT '{}'::jsonb,
|
||||
created_at timestamp(6) without time zone NOT NULL,
|
||||
updated_at timestamp(6) without time zone NOT NULL
|
||||
@@ -5465,6 +5470,34 @@ CREATE UNIQUE INDEX idx_domain_fa_posts_on_fa_id ON public.domain_posts USING bt
|
||||
CREATE UNIQUE INDEX idx_domain_fa_users_on_url_name ON public.domain_users USING btree (((json_attributes ->> 'url_name'::text))) WHERE (type = 'Domain::User::FaUser'::public.domain_user_type);
|
||||
|
||||
|
||||
--
|
||||
-- Name: idx_domain_inkbunny_post_files_on_ib_id; Type: INDEX; Schema: public; Owner: -; Tablespace: mirai
|
||||
--
|
||||
|
||||
CREATE UNIQUE INDEX idx_domain_inkbunny_post_files_on_ib_id ON public.domain_post_files USING btree ((((json_attributes ->> 'ib_id'::text))::integer)) WHERE (type = 'Domain::PostFile::InkbunnyPostFile'::public.domain_post_file_type);
|
||||
|
||||
|
||||
--
|
||||
-- Name: idx_domain_inkbunny_posts_on_ib_id; Type: INDEX; Schema: public; Owner: -; Tablespace: mirai
|
||||
--
|
||||
|
||||
CREATE UNIQUE INDEX idx_domain_inkbunny_posts_on_ib_id ON public.domain_posts USING btree ((((json_attributes ->> 'ib_id'::text))::integer)) WHERE (type = 'Domain::Post::InkbunnyPost'::public.domain_post_type);
|
||||
|
||||
|
||||
--
|
||||
-- Name: idx_domain_inkbunny_users_on_ib_id; Type: INDEX; Schema: public; Owner: -; Tablespace: mirai
|
||||
--
|
||||
|
||||
CREATE UNIQUE INDEX idx_domain_inkbunny_users_on_ib_id ON public.domain_users USING btree ((((json_attributes ->> 'ib_id'::text))::integer)) WHERE (type = 'Domain::User::InkbunnyUser'::public.domain_user_type);
|
||||
|
||||
|
||||
--
|
||||
-- Name: idx_domain_inkbunny_users_on_name; Type: INDEX; Schema: public; Owner: -; Tablespace: mirai
|
||||
--
|
||||
|
||||
CREATE UNIQUE INDEX idx_domain_inkbunny_users_on_name ON public.domain_users USING btree (((json_attributes ->> 'name'::text))) WHERE (type = 'Domain::User::InkbunnyUser'::public.domain_user_type);
|
||||
|
||||
|
||||
--
|
||||
-- Name: idx_domain_users_on_migrated_user_favs_at; Type: INDEX; Schema: public; Owner: -; Tablespace: mirai
|
||||
--
|
||||
@@ -6767,6 +6800,13 @@ CREATE INDEX index_domain_post_files_on_log_entry_id ON public.domain_post_files
|
||||
CREATE INDEX index_domain_post_files_on_post_id ON public.domain_post_files USING btree (post_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: index_domain_post_files_on_type; Type: INDEX; Schema: public; Owner: -; Tablespace: mirai
|
||||
--
|
||||
|
||||
CREATE INDEX index_domain_post_files_on_type ON public.domain_post_files USING btree (type);
|
||||
|
||||
|
||||
--
|
||||
-- Name: index_domain_post_group_joins_on_group_id_and_post_id; Type: INDEX; Schema: public; Owner: -; Tablespace: mirai
|
||||
--
|
||||
@@ -8413,7 +8453,7 @@ ALTER TABLE ONLY public.domain_twitter_tweets
|
||||
SET search_path TO "$user", public;
|
||||
|
||||
INSERT INTO "schema_migrations" (version) VALUES
|
||||
('20250205035529'),
|
||||
('20250206224121'),
|
||||
('20250203235035'),
|
||||
('20250131060105'),
|
||||
('20250131055824'),
|
||||
|
||||
3
sorbet/rbi/dsl/application_controller.rbi
generated
3
sorbet/rbi/dsl/application_controller.rbi
generated
@@ -32,6 +32,9 @@ class ApplicationController
|
||||
include ::Domain::E621::PostsHelper
|
||||
include ::Domain::Fa::PostsHelper
|
||||
include ::Domain::Fa::UsersHelper
|
||||
include ::HelpersInterface
|
||||
include ::Domain::PostsHelper
|
||||
include ::Domain::UsersHelper
|
||||
include ::GoodJobHelper
|
||||
include ::IndexablePostsHelper
|
||||
include ::LogEntriesHelper
|
||||
|
||||
3
sorbet/rbi/dsl/devise_controller.rbi
generated
3
sorbet/rbi/dsl/devise_controller.rbi
generated
@@ -29,6 +29,9 @@ class DeviseController
|
||||
include ::Domain::E621::PostsHelper
|
||||
include ::Domain::Fa::PostsHelper
|
||||
include ::Domain::Fa::UsersHelper
|
||||
include ::HelpersInterface
|
||||
include ::Domain::PostsHelper
|
||||
include ::Domain::UsersHelper
|
||||
include ::GoodJobHelper
|
||||
include ::IndexablePostsHelper
|
||||
include ::LogEntriesHelper
|
||||
|
||||
@@ -15,13 +15,13 @@ class Domain::Fa::Job::UserIncrementalJob
|
||||
|
||||
sig do
|
||||
params(
|
||||
args: T.untyped,
|
||||
args: T::Hash[::Symbol, T.untyped],
|
||||
block: T.nilable(T.proc.params(job: Domain::Fa::Job::UserIncrementalJob).void)
|
||||
).returns(T.any(Domain::Fa::Job::UserIncrementalJob, FalseClass))
|
||||
end
|
||||
def perform_later(args, &block); end
|
||||
|
||||
sig { params(args: T.untyped).returns(T.untyped) }
|
||||
sig { params(args: T::Hash[::Symbol, T.untyped]).void }
|
||||
def perform_now(args); end
|
||||
end
|
||||
end
|
||||
|
||||
4
sorbet/rbi/dsl/domain/fa/job/user_page_job.rbi
generated
4
sorbet/rbi/dsl/domain/fa/job/user_page_job.rbi
generated
@@ -15,13 +15,13 @@ class Domain::Fa::Job::UserPageJob
|
||||
|
||||
sig do
|
||||
params(
|
||||
args: T.untyped,
|
||||
args: T::Hash[::Symbol, T.untyped],
|
||||
block: T.nilable(T.proc.params(job: Domain::Fa::Job::UserPageJob).void)
|
||||
).returns(T.any(Domain::Fa::Job::UserPageJob, FalseClass))
|
||||
end
|
||||
def perform_later(args, &block); end
|
||||
|
||||
sig { params(args: T.untyped).returns(T.untyped) }
|
||||
sig { params(args: T::Hash[::Symbol, T.untyped]).void }
|
||||
def perform_now(args); end
|
||||
end
|
||||
end
|
||||
|
||||
67
sorbet/rbi/dsl/domain/post.rbi
generated
67
sorbet/rbi/dsl/domain/post.rbi
generated
@@ -789,6 +789,61 @@ class Domain::Post
|
||||
sig { void }
|
||||
def json_attributes_will_change!; end
|
||||
|
||||
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
|
||||
def posted_at; end
|
||||
|
||||
sig { params(value: T.nilable(::ActiveSupport::TimeWithZone)).returns(T.nilable(::ActiveSupport::TimeWithZone)) }
|
||||
def posted_at=(value); end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def posted_at?; end
|
||||
|
||||
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
|
||||
def posted_at_before_last_save; end
|
||||
|
||||
sig { returns(T.untyped) }
|
||||
def posted_at_before_type_cast; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def posted_at_came_from_user?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
|
||||
def posted_at_change; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
|
||||
def posted_at_change_to_be_saved; end
|
||||
|
||||
sig do
|
||||
params(
|
||||
from: T.nilable(::ActiveSupport::TimeWithZone),
|
||||
to: T.nilable(::ActiveSupport::TimeWithZone)
|
||||
).returns(T::Boolean)
|
||||
end
|
||||
def posted_at_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
|
||||
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
|
||||
def posted_at_in_database; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
|
||||
def posted_at_previous_change; end
|
||||
|
||||
sig do
|
||||
params(
|
||||
from: T.nilable(::ActiveSupport::TimeWithZone),
|
||||
to: T.nilable(::ActiveSupport::TimeWithZone)
|
||||
).returns(T::Boolean)
|
||||
end
|
||||
def posted_at_previously_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
|
||||
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
|
||||
def posted_at_previously_was; end
|
||||
|
||||
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
|
||||
def posted_at_was; end
|
||||
|
||||
sig { void }
|
||||
def posted_at_will_change!; end
|
||||
|
||||
sig { void }
|
||||
def restore_created_at!; end
|
||||
|
||||
@@ -801,6 +856,9 @@ class Domain::Post
|
||||
sig { void }
|
||||
def restore_json_attributes!; end
|
||||
|
||||
sig { void }
|
||||
def restore_posted_at!; end
|
||||
|
||||
sig { void }
|
||||
def restore_type!; end
|
||||
|
||||
@@ -831,6 +889,12 @@ class Domain::Post
|
||||
sig { returns(T::Boolean) }
|
||||
def saved_change_to_json_attributes?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
|
||||
def saved_change_to_posted_at; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def saved_change_to_posted_at?; end
|
||||
|
||||
sig { returns(T.nilable([T.untyped, T.untyped])) }
|
||||
def saved_change_to_type; end
|
||||
|
||||
@@ -955,6 +1019,9 @@ class Domain::Post
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_json_attributes?; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_posted_at?; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_type?; end
|
||||
|
||||
|
||||
67
sorbet/rbi/dsl/domain/post/e621_post.rbi
generated
67
sorbet/rbi/dsl/domain/post/e621_post.rbi
generated
@@ -1428,6 +1428,61 @@ class Domain::Post::E621Post
|
||||
sig { void }
|
||||
def pools_array_will_change!; end
|
||||
|
||||
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
|
||||
def posted_at; end
|
||||
|
||||
sig { params(value: T.nilable(::ActiveSupport::TimeWithZone)).returns(T.nilable(::ActiveSupport::TimeWithZone)) }
|
||||
def posted_at=(value); end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def posted_at?; end
|
||||
|
||||
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
|
||||
def posted_at_before_last_save; end
|
||||
|
||||
sig { returns(T.untyped) }
|
||||
def posted_at_before_type_cast; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def posted_at_came_from_user?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
|
||||
def posted_at_change; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
|
||||
def posted_at_change_to_be_saved; end
|
||||
|
||||
sig do
|
||||
params(
|
||||
from: T.nilable(::ActiveSupport::TimeWithZone),
|
||||
to: T.nilable(::ActiveSupport::TimeWithZone)
|
||||
).returns(T::Boolean)
|
||||
end
|
||||
def posted_at_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
|
||||
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
|
||||
def posted_at_in_database; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
|
||||
def posted_at_previous_change; end
|
||||
|
||||
sig do
|
||||
params(
|
||||
from: T.nilable(::ActiveSupport::TimeWithZone),
|
||||
to: T.nilable(::ActiveSupport::TimeWithZone)
|
||||
).returns(T::Boolean)
|
||||
end
|
||||
def posted_at_previously_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
|
||||
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
|
||||
def posted_at_previously_was; end
|
||||
|
||||
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
|
||||
def posted_at_was; end
|
||||
|
||||
sig { void }
|
||||
def posted_at_will_change!; end
|
||||
|
||||
sig { returns(T.untyped) }
|
||||
def prev_md5s; end
|
||||
|
||||
@@ -1560,6 +1615,9 @@ class Domain::Post::E621Post
|
||||
sig { void }
|
||||
def restore_pools_array!; end
|
||||
|
||||
sig { void }
|
||||
def restore_posted_at!; end
|
||||
|
||||
sig { void }
|
||||
def restore_prev_md5s!; end
|
||||
|
||||
@@ -1677,6 +1735,12 @@ class Domain::Post::E621Post
|
||||
sig { returns(T::Boolean) }
|
||||
def saved_change_to_pools_array?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
|
||||
def saved_change_to_posted_at; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def saved_change_to_posted_at?; end
|
||||
|
||||
sig { returns(T.nilable([T.untyped, T.untyped])) }
|
||||
def saved_change_to_prev_md5s; end
|
||||
|
||||
@@ -2210,6 +2274,9 @@ class Domain::Post::E621Post
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_pools_array?; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_posted_at?; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_prev_md5s?; end
|
||||
|
||||
|
||||
707
sorbet/rbi/dsl/domain/post/inkbunny_post.rbi
generated
707
sorbet/rbi/dsl/domain/post/inkbunny_post.rbi
generated
@@ -451,27 +451,57 @@ class Domain::Post::InkbunnyPost
|
||||
sig { params(args: T.untyped, blk: T.untyped).returns(::Domain::User::InkbunnyUser) }
|
||||
def build_creator(*args, &blk); end
|
||||
|
||||
sig { params(args: T.untyped, blk: T.untyped).returns(::HttpLogEntry) }
|
||||
def build_deep_update_log_entry(*args, &blk); end
|
||||
|
||||
sig { params(args: T.untyped, blk: T.untyped).returns(::Domain::UserPostCreation) }
|
||||
def build_primary_user_post_creation(*args, &blk); end
|
||||
|
||||
sig { params(args: T.untyped, blk: T.untyped).returns(::HttpLogEntry) }
|
||||
def build_shallow_update_log_entry(*args, &blk); end
|
||||
|
||||
sig { params(args: T.untyped, blk: T.untyped).returns(::Domain::User::InkbunnyUser) }
|
||||
def create_creator(*args, &blk); end
|
||||
|
||||
sig { params(args: T.untyped, blk: T.untyped).returns(::Domain::User::InkbunnyUser) }
|
||||
def create_creator!(*args, &blk); end
|
||||
|
||||
sig { params(args: T.untyped, blk: T.untyped).returns(::HttpLogEntry) }
|
||||
def create_deep_update_log_entry(*args, &blk); end
|
||||
|
||||
sig { params(args: T.untyped, blk: T.untyped).returns(::HttpLogEntry) }
|
||||
def create_deep_update_log_entry!(*args, &blk); end
|
||||
|
||||
sig { params(args: T.untyped, blk: T.untyped).returns(::Domain::UserPostCreation) }
|
||||
def create_primary_user_post_creation(*args, &blk); end
|
||||
|
||||
sig { params(args: T.untyped, blk: T.untyped).returns(::Domain::UserPostCreation) }
|
||||
def create_primary_user_post_creation!(*args, &blk); end
|
||||
|
||||
sig { params(args: T.untyped, blk: T.untyped).returns(::HttpLogEntry) }
|
||||
def create_shallow_update_log_entry(*args, &blk); end
|
||||
|
||||
sig { params(args: T.untyped, blk: T.untyped).returns(::HttpLogEntry) }
|
||||
def create_shallow_update_log_entry!(*args, &blk); end
|
||||
|
||||
sig { returns(T.nilable(::Domain::User::InkbunnyUser)) }
|
||||
def creator; end
|
||||
|
||||
sig { params(value: T.nilable(::Domain::User::InkbunnyUser)).void }
|
||||
def creator=(value); end
|
||||
|
||||
sig { returns(T.nilable(::HttpLogEntry)) }
|
||||
def deep_update_log_entry; end
|
||||
|
||||
sig { params(value: T.nilable(::HttpLogEntry)).void }
|
||||
def deep_update_log_entry=(value); end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def deep_update_log_entry_changed?; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def deep_update_log_entry_previously_changed?; end
|
||||
|
||||
sig { returns(T::Array[T.untyped]) }
|
||||
def faving_user_ids; end
|
||||
|
||||
@@ -492,12 +522,12 @@ class Domain::Post::InkbunnyPost
|
||||
sig { params(ids: T::Array[T.untyped]).returns(T::Array[T.untyped]) }
|
||||
def file_ids=(ids); end
|
||||
|
||||
# This method is created by ActiveRecord on the `Domain::Post` class because it declared `has_many :files`.
|
||||
# This method is created by ActiveRecord on the `Domain::Post::InkbunnyPost` class because it declared `has_many :files`.
|
||||
# 🔗 [Rails guide for `has_many` association](https://guides.rubyonrails.org/association_basics.html#the-has-many-association)
|
||||
sig { returns(::Domain::PostFile::PrivateCollectionProxy) }
|
||||
sig { returns(::Domain::PostFile::InkbunnyPostFile::PrivateCollectionProxy) }
|
||||
def files; end
|
||||
|
||||
sig { params(value: T::Enumerable[::Domain::PostFile]).void }
|
||||
sig { params(value: T::Enumerable[::Domain::PostFile::InkbunnyPostFile]).void }
|
||||
def files=(value); end
|
||||
|
||||
sig { returns(T::Array[T.untyped]) }
|
||||
@@ -537,15 +567,39 @@ class Domain::Post::InkbunnyPost
|
||||
sig { returns(T.nilable(::Domain::User::InkbunnyUser)) }
|
||||
def reload_creator; end
|
||||
|
||||
sig { returns(T.nilable(::HttpLogEntry)) }
|
||||
def reload_deep_update_log_entry; end
|
||||
|
||||
sig { returns(T.nilable(::Domain::UserPostCreation)) }
|
||||
def reload_primary_user_post_creation; end
|
||||
|
||||
sig { returns(T.nilable(::HttpLogEntry)) }
|
||||
def reload_shallow_update_log_entry; end
|
||||
|
||||
sig { void }
|
||||
def reset_creator; end
|
||||
|
||||
sig { void }
|
||||
def reset_deep_update_log_entry; end
|
||||
|
||||
sig { void }
|
||||
def reset_primary_user_post_creation; end
|
||||
|
||||
sig { void }
|
||||
def reset_shallow_update_log_entry; end
|
||||
|
||||
sig { returns(T.nilable(::HttpLogEntry)) }
|
||||
def shallow_update_log_entry; end
|
||||
|
||||
sig { params(value: T.nilable(::HttpLogEntry)).void }
|
||||
def shallow_update_log_entry=(value); end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def shallow_update_log_entry_changed?; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def shallow_update_log_entry_previously_changed?; end
|
||||
|
||||
sig { returns(T::Array[T.untyped]) }
|
||||
def user_post_creation_ids; end
|
||||
|
||||
@@ -775,6 +829,51 @@ class Domain::Post::InkbunnyPost
|
||||
sig { void }
|
||||
def created_at_will_change!; end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def deep_update_log_entry_id; end
|
||||
|
||||
sig { params(value: T.nilable(::Integer)).returns(T.nilable(::Integer)) }
|
||||
def deep_update_log_entry_id=(value); end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def deep_update_log_entry_id?; end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def deep_update_log_entry_id_before_last_save; end
|
||||
|
||||
sig { returns(T.untyped) }
|
||||
def deep_update_log_entry_id_before_type_cast; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def deep_update_log_entry_id_came_from_user?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) }
|
||||
def deep_update_log_entry_id_change; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) }
|
||||
def deep_update_log_entry_id_change_to_be_saved; end
|
||||
|
||||
sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) }
|
||||
def deep_update_log_entry_id_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def deep_update_log_entry_id_in_database; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) }
|
||||
def deep_update_log_entry_id_previous_change; end
|
||||
|
||||
sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) }
|
||||
def deep_update_log_entry_id_previously_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def deep_update_log_entry_id_previously_was; end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def deep_update_log_entry_id_was; end
|
||||
|
||||
sig { void }
|
||||
def deep_update_log_entry_id_will_change!; end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def ib_id; end
|
||||
|
||||
@@ -955,6 +1054,341 @@ class Domain::Post::InkbunnyPost
|
||||
sig { void }
|
||||
def json_attributes_will_change!; end
|
||||
|
||||
sig { returns(T.untyped) }
|
||||
def keywords; end
|
||||
|
||||
sig { params(value: T.untyped).returns(T.untyped) }
|
||||
def keywords=(value); end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def keywords?; end
|
||||
|
||||
sig { returns(T.untyped) }
|
||||
def keywords_before_last_save; end
|
||||
|
||||
sig { returns(T.untyped) }
|
||||
def keywords_before_type_cast; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def keywords_came_from_user?; end
|
||||
|
||||
sig { returns(T.nilable([T.untyped, T.untyped])) }
|
||||
def keywords_change; end
|
||||
|
||||
sig { returns(T.nilable([T.untyped, T.untyped])) }
|
||||
def keywords_change_to_be_saved; end
|
||||
|
||||
sig { params(from: T.untyped, to: T.untyped).returns(T::Boolean) }
|
||||
def keywords_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
|
||||
sig { returns(T.untyped) }
|
||||
def keywords_in_database; end
|
||||
|
||||
sig { returns(T.nilable([T.untyped, T.untyped])) }
|
||||
def keywords_previous_change; end
|
||||
|
||||
sig { params(from: T.untyped, to: T.untyped).returns(T::Boolean) }
|
||||
def keywords_previously_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
|
||||
sig { returns(T.untyped) }
|
||||
def keywords_previously_was; end
|
||||
|
||||
sig { returns(T.untyped) }
|
||||
def keywords_was; end
|
||||
|
||||
sig { void }
|
||||
def keywords_will_change!; end
|
||||
|
||||
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
|
||||
def last_file_updated_at; end
|
||||
|
||||
sig { params(value: T.nilable(::ActiveSupport::TimeWithZone)).returns(T.nilable(::ActiveSupport::TimeWithZone)) }
|
||||
def last_file_updated_at=(value); end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def last_file_updated_at?; end
|
||||
|
||||
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
|
||||
def last_file_updated_at_before_last_save; end
|
||||
|
||||
sig { returns(T.untyped) }
|
||||
def last_file_updated_at_before_type_cast; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def last_file_updated_at_came_from_user?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
|
||||
def last_file_updated_at_change; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
|
||||
def last_file_updated_at_change_to_be_saved; end
|
||||
|
||||
sig do
|
||||
params(
|
||||
from: T.nilable(::ActiveSupport::TimeWithZone),
|
||||
to: T.nilable(::ActiveSupport::TimeWithZone)
|
||||
).returns(T::Boolean)
|
||||
end
|
||||
def last_file_updated_at_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
|
||||
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
|
||||
def last_file_updated_at_in_database; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
|
||||
def last_file_updated_at_previous_change; end
|
||||
|
||||
sig do
|
||||
params(
|
||||
from: T.nilable(::ActiveSupport::TimeWithZone),
|
||||
to: T.nilable(::ActiveSupport::TimeWithZone)
|
||||
).returns(T::Boolean)
|
||||
end
|
||||
def last_file_updated_at_previously_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
|
||||
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
|
||||
def last_file_updated_at_previously_was; end
|
||||
|
||||
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
|
||||
def last_file_updated_at_was; end
|
||||
|
||||
sig { void }
|
||||
def last_file_updated_at_will_change!; end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def num_comments; end
|
||||
|
||||
sig { params(value: T.nilable(::Integer)).returns(T.nilable(::Integer)) }
|
||||
def num_comments=(value); end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def num_comments?; end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def num_comments_before_last_save; end
|
||||
|
||||
sig { returns(T.untyped) }
|
||||
def num_comments_before_type_cast; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def num_comments_came_from_user?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) }
|
||||
def num_comments_change; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) }
|
||||
def num_comments_change_to_be_saved; end
|
||||
|
||||
sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) }
|
||||
def num_comments_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def num_comments_in_database; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) }
|
||||
def num_comments_previous_change; end
|
||||
|
||||
sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) }
|
||||
def num_comments_previously_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def num_comments_previously_was; end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def num_comments_was; end
|
||||
|
||||
sig { void }
|
||||
def num_comments_will_change!; end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def num_favs; end
|
||||
|
||||
sig { params(value: T.nilable(::Integer)).returns(T.nilable(::Integer)) }
|
||||
def num_favs=(value); end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def num_favs?; end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def num_favs_before_last_save; end
|
||||
|
||||
sig { returns(T.untyped) }
|
||||
def num_favs_before_type_cast; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def num_favs_came_from_user?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) }
|
||||
def num_favs_change; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) }
|
||||
def num_favs_change_to_be_saved; end
|
||||
|
||||
sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) }
|
||||
def num_favs_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def num_favs_in_database; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) }
|
||||
def num_favs_previous_change; end
|
||||
|
||||
sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) }
|
||||
def num_favs_previously_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def num_favs_previously_was; end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def num_favs_was; end
|
||||
|
||||
sig { void }
|
||||
def num_favs_will_change!; end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def num_files; end
|
||||
|
||||
sig { params(value: T.nilable(::Integer)).returns(T.nilable(::Integer)) }
|
||||
def num_files=(value); end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def num_files?; end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def num_files_before_last_save; end
|
||||
|
||||
sig { returns(T.untyped) }
|
||||
def num_files_before_type_cast; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def num_files_came_from_user?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) }
|
||||
def num_files_change; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) }
|
||||
def num_files_change_to_be_saved; end
|
||||
|
||||
sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) }
|
||||
def num_files_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def num_files_in_database; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) }
|
||||
def num_files_previous_change; end
|
||||
|
||||
sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) }
|
||||
def num_files_previously_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def num_files_previously_was; end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def num_files_was; end
|
||||
|
||||
sig { void }
|
||||
def num_files_will_change!; end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def num_views; end
|
||||
|
||||
sig { params(value: T.nilable(::Integer)).returns(T.nilable(::Integer)) }
|
||||
def num_views=(value); end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def num_views?; end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def num_views_before_last_save; end
|
||||
|
||||
sig { returns(T.untyped) }
|
||||
def num_views_before_type_cast; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def num_views_came_from_user?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) }
|
||||
def num_views_change; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) }
|
||||
def num_views_change_to_be_saved; end
|
||||
|
||||
sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) }
|
||||
def num_views_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def num_views_in_database; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) }
|
||||
def num_views_previous_change; end
|
||||
|
||||
sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) }
|
||||
def num_views_previously_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def num_views_previously_was; end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def num_views_was; end
|
||||
|
||||
sig { void }
|
||||
def num_views_will_change!; end
|
||||
|
||||
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
|
||||
def posted_at; end
|
||||
|
||||
sig { params(value: T.nilable(::ActiveSupport::TimeWithZone)).returns(T.nilable(::ActiveSupport::TimeWithZone)) }
|
||||
def posted_at=(value); end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def posted_at?; end
|
||||
|
||||
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
|
||||
def posted_at_before_last_save; end
|
||||
|
||||
sig { returns(T.untyped) }
|
||||
def posted_at_before_type_cast; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def posted_at_came_from_user?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
|
||||
def posted_at_change; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
|
||||
def posted_at_change_to_be_saved; end
|
||||
|
||||
sig do
|
||||
params(
|
||||
from: T.nilable(::ActiveSupport::TimeWithZone),
|
||||
to: T.nilable(::ActiveSupport::TimeWithZone)
|
||||
).returns(T::Boolean)
|
||||
end
|
||||
def posted_at_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
|
||||
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
|
||||
def posted_at_in_database; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
|
||||
def posted_at_previous_change; end
|
||||
|
||||
sig do
|
||||
params(
|
||||
from: T.nilable(::ActiveSupport::TimeWithZone),
|
||||
to: T.nilable(::ActiveSupport::TimeWithZone)
|
||||
).returns(T::Boolean)
|
||||
end
|
||||
def posted_at_previously_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
|
||||
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
|
||||
def posted_at_previously_was; end
|
||||
|
||||
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
|
||||
def posted_at_was; end
|
||||
|
||||
sig { void }
|
||||
def posted_at_will_change!; end
|
||||
|
||||
sig { returns(T.nilable(::String)) }
|
||||
def rating; end
|
||||
|
||||
@@ -1003,6 +1437,9 @@ class Domain::Post::InkbunnyPost
|
||||
sig { void }
|
||||
def restore_created_at!; end
|
||||
|
||||
sig { void }
|
||||
def restore_deep_update_log_entry_id!; end
|
||||
|
||||
sig { void }
|
||||
def restore_ib_id!; end
|
||||
|
||||
@@ -1015,27 +1452,63 @@ class Domain::Post::InkbunnyPost
|
||||
sig { void }
|
||||
def restore_json_attributes!; end
|
||||
|
||||
sig { void }
|
||||
def restore_keywords!; end
|
||||
|
||||
sig { void }
|
||||
def restore_last_file_updated_at!; end
|
||||
|
||||
sig { void }
|
||||
def restore_num_comments!; end
|
||||
|
||||
sig { void }
|
||||
def restore_num_favs!; end
|
||||
|
||||
sig { void }
|
||||
def restore_num_files!; end
|
||||
|
||||
sig { void }
|
||||
def restore_num_views!; end
|
||||
|
||||
sig { void }
|
||||
def restore_posted_at!; end
|
||||
|
||||
sig { void }
|
||||
def restore_rating!; end
|
||||
|
||||
sig { void }
|
||||
def restore_shallow_update_log_entry_id!; end
|
||||
|
||||
sig { void }
|
||||
def restore_state!; end
|
||||
|
||||
sig { void }
|
||||
def restore_submission_type!; end
|
||||
|
||||
sig { void }
|
||||
def restore_title!; end
|
||||
|
||||
sig { void }
|
||||
def restore_type!; end
|
||||
|
||||
sig { void }
|
||||
def restore_updated_at!; end
|
||||
|
||||
sig { void }
|
||||
def restore_writing!; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
|
||||
def saved_change_to_created_at; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def saved_change_to_created_at?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) }
|
||||
def saved_change_to_deep_update_log_entry_id; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def saved_change_to_deep_update_log_entry_id?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) }
|
||||
def saved_change_to_ib_id; end
|
||||
|
||||
@@ -1060,12 +1533,60 @@ class Domain::Post::InkbunnyPost
|
||||
sig { returns(T::Boolean) }
|
||||
def saved_change_to_json_attributes?; end
|
||||
|
||||
sig { returns(T.nilable([T.untyped, T.untyped])) }
|
||||
def saved_change_to_keywords; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def saved_change_to_keywords?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
|
||||
def saved_change_to_last_file_updated_at; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def saved_change_to_last_file_updated_at?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) }
|
||||
def saved_change_to_num_comments; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def saved_change_to_num_comments?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) }
|
||||
def saved_change_to_num_favs; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def saved_change_to_num_favs?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) }
|
||||
def saved_change_to_num_files; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def saved_change_to_num_files?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) }
|
||||
def saved_change_to_num_views; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def saved_change_to_num_views?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
|
||||
def saved_change_to_posted_at; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def saved_change_to_posted_at?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::String), T.nilable(::String)])) }
|
||||
def saved_change_to_rating; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def saved_change_to_rating?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) }
|
||||
def saved_change_to_shallow_update_log_entry_id; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def saved_change_to_shallow_update_log_entry_id?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::String), T.nilable(::String)])) }
|
||||
def saved_change_to_state; end
|
||||
|
||||
@@ -1078,6 +1599,12 @@ class Domain::Post::InkbunnyPost
|
||||
sig { returns(T::Boolean) }
|
||||
def saved_change_to_submission_type?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::String), T.nilable(::String)])) }
|
||||
def saved_change_to_title; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def saved_change_to_title?; end
|
||||
|
||||
sig { returns(T.nilable([T.untyped, T.untyped])) }
|
||||
def saved_change_to_type; end
|
||||
|
||||
@@ -1090,6 +1617,57 @@ class Domain::Post::InkbunnyPost
|
||||
sig { returns(T::Boolean) }
|
||||
def saved_change_to_updated_at?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::String), T.nilable(::String)])) }
|
||||
def saved_change_to_writing; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def saved_change_to_writing?; end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def shallow_update_log_entry_id; end
|
||||
|
||||
sig { params(value: T.nilable(::Integer)).returns(T.nilable(::Integer)) }
|
||||
def shallow_update_log_entry_id=(value); end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def shallow_update_log_entry_id?; end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def shallow_update_log_entry_id_before_last_save; end
|
||||
|
||||
sig { returns(T.untyped) }
|
||||
def shallow_update_log_entry_id_before_type_cast; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def shallow_update_log_entry_id_came_from_user?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) }
|
||||
def shallow_update_log_entry_id_change; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) }
|
||||
def shallow_update_log_entry_id_change_to_be_saved; end
|
||||
|
||||
sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) }
|
||||
def shallow_update_log_entry_id_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def shallow_update_log_entry_id_in_database; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) }
|
||||
def shallow_update_log_entry_id_previous_change; end
|
||||
|
||||
sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) }
|
||||
def shallow_update_log_entry_id_previously_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def shallow_update_log_entry_id_previously_was; end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def shallow_update_log_entry_id_was; end
|
||||
|
||||
sig { void }
|
||||
def shallow_update_log_entry_id_will_change!; end
|
||||
|
||||
sig { returns(T.nilable(::String)) }
|
||||
def state; end
|
||||
|
||||
@@ -1180,6 +1758,51 @@ class Domain::Post::InkbunnyPost
|
||||
sig { void }
|
||||
def submission_type_will_change!; end
|
||||
|
||||
sig { returns(T.nilable(::String)) }
|
||||
def title; end
|
||||
|
||||
sig { params(value: T.nilable(::String)).returns(T.nilable(::String)) }
|
||||
def title=(value); end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def title?; end
|
||||
|
||||
sig { returns(T.nilable(::String)) }
|
||||
def title_before_last_save; end
|
||||
|
||||
sig { returns(T.untyped) }
|
||||
def title_before_type_cast; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def title_came_from_user?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::String), T.nilable(::String)])) }
|
||||
def title_change; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::String), T.nilable(::String)])) }
|
||||
def title_change_to_be_saved; end
|
||||
|
||||
sig { params(from: T.nilable(::String), to: T.nilable(::String)).returns(T::Boolean) }
|
||||
def title_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
|
||||
sig { returns(T.nilable(::String)) }
|
||||
def title_in_database; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::String), T.nilable(::String)])) }
|
||||
def title_previous_change; end
|
||||
|
||||
sig { params(from: T.nilable(::String), to: T.nilable(::String)).returns(T::Boolean) }
|
||||
def title_previously_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
|
||||
sig { returns(T.nilable(::String)) }
|
||||
def title_previously_was; end
|
||||
|
||||
sig { returns(T.nilable(::String)) }
|
||||
def title_was; end
|
||||
|
||||
sig { void }
|
||||
def title_will_change!; end
|
||||
|
||||
sig { returns(T.untyped) }
|
||||
def type; end
|
||||
|
||||
@@ -1283,6 +1906,9 @@ class Domain::Post::InkbunnyPost
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_created_at?; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_deep_update_log_entry_id?; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_ib_id?; end
|
||||
|
||||
@@ -1295,20 +1921,95 @@ class Domain::Post::InkbunnyPost
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_json_attributes?; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_keywords?; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_last_file_updated_at?; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_num_comments?; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_num_favs?; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_num_files?; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_num_views?; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_posted_at?; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_rating?; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_shallow_update_log_entry_id?; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_state?; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_submission_type?; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_title?; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_type?; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_updated_at?; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_writing?; end
|
||||
|
||||
sig { returns(T.nilable(::String)) }
|
||||
def writing; end
|
||||
|
||||
sig { params(value: T.nilable(::String)).returns(T.nilable(::String)) }
|
||||
def writing=(value); end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def writing?; end
|
||||
|
||||
sig { returns(T.nilable(::String)) }
|
||||
def writing_before_last_save; end
|
||||
|
||||
sig { returns(T.untyped) }
|
||||
def writing_before_type_cast; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def writing_came_from_user?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::String), T.nilable(::String)])) }
|
||||
def writing_change; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::String), T.nilable(::String)])) }
|
||||
def writing_change_to_be_saved; end
|
||||
|
||||
sig { params(from: T.nilable(::String), to: T.nilable(::String)).returns(T::Boolean) }
|
||||
def writing_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
|
||||
sig { returns(T.nilable(::String)) }
|
||||
def writing_in_database; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::String), T.nilable(::String)])) }
|
||||
def writing_previous_change; end
|
||||
|
||||
sig { params(from: T.nilable(::String), to: T.nilable(::String)).returns(T::Boolean) }
|
||||
def writing_previously_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
|
||||
sig { returns(T.nilable(::String)) }
|
||||
def writing_previously_was; end
|
||||
|
||||
sig { returns(T.nilable(::String)) }
|
||||
def writing_was; end
|
||||
|
||||
sig { void }
|
||||
def writing_will_change!; end
|
||||
end
|
||||
|
||||
module GeneratedRelationMethods
|
||||
|
||||
255
sorbet/rbi/dsl/domain/post_file.rbi
generated
255
sorbet/rbi/dsl/domain/post_file.rbi
generated
@@ -411,12 +411,33 @@ class Domain::PostFile
|
||||
end
|
||||
|
||||
module GeneratedAssociationMethods
|
||||
sig { returns(T.nilable(::BlobEntry)) }
|
||||
def blob; end
|
||||
|
||||
sig { params(value: T.nilable(::BlobEntry)).void }
|
||||
def blob=(value); end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def blob_changed?; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def blob_previously_changed?; end
|
||||
|
||||
sig { params(args: T.untyped, blk: T.untyped).returns(::BlobEntry) }
|
||||
def build_blob(*args, &blk); end
|
||||
|
||||
sig { params(args: T.untyped, blk: T.untyped).returns(::HttpLogEntry) }
|
||||
def build_log_entry(*args, &blk); end
|
||||
|
||||
sig { params(args: T.untyped, blk: T.untyped).returns(::Domain::Post) }
|
||||
def build_post(*args, &blk); end
|
||||
|
||||
sig { params(args: T.untyped, blk: T.untyped).returns(::BlobEntry) }
|
||||
def create_blob(*args, &blk); end
|
||||
|
||||
sig { params(args: T.untyped, blk: T.untyped).returns(::BlobEntry) }
|
||||
def create_blob!(*args, &blk); end
|
||||
|
||||
sig { params(args: T.untyped, blk: T.untyped).returns(::HttpLogEntry) }
|
||||
def create_log_entry(*args, &blk); end
|
||||
|
||||
@@ -453,12 +474,18 @@ class Domain::PostFile
|
||||
sig { returns(T::Boolean) }
|
||||
def post_previously_changed?; end
|
||||
|
||||
sig { returns(T.nilable(::BlobEntry)) }
|
||||
def reload_blob; end
|
||||
|
||||
sig { returns(T.nilable(::HttpLogEntry)) }
|
||||
def reload_log_entry; end
|
||||
|
||||
sig { returns(T.nilable(::Domain::Post)) }
|
||||
def reload_post; end
|
||||
|
||||
sig { void }
|
||||
def reset_blob; end
|
||||
|
||||
sig { void }
|
||||
def reset_log_entry; end
|
||||
|
||||
@@ -611,6 +638,51 @@ class Domain::PostFile
|
||||
end
|
||||
|
||||
module GeneratedAttributeMethods
|
||||
sig { returns(T.nilable(::String)) }
|
||||
def blob_sha256; end
|
||||
|
||||
sig { params(value: T.nilable(::String)).returns(T.nilable(::String)) }
|
||||
def blob_sha256=(value); end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def blob_sha256?; end
|
||||
|
||||
sig { returns(T.nilable(::String)) }
|
||||
def blob_sha256_before_last_save; end
|
||||
|
||||
sig { returns(T.untyped) }
|
||||
def blob_sha256_before_type_cast; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def blob_sha256_came_from_user?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::String), T.nilable(::String)])) }
|
||||
def blob_sha256_change; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::String), T.nilable(::String)])) }
|
||||
def blob_sha256_change_to_be_saved; end
|
||||
|
||||
sig { params(from: T.nilable(::String), to: T.nilable(::String)).returns(T::Boolean) }
|
||||
def blob_sha256_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
|
||||
sig { returns(T.nilable(::String)) }
|
||||
def blob_sha256_in_database; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::String), T.nilable(::String)])) }
|
||||
def blob_sha256_previous_change; end
|
||||
|
||||
sig { params(from: T.nilable(::String), to: T.nilable(::String)).returns(T::Boolean) }
|
||||
def blob_sha256_previously_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
|
||||
sig { returns(T.nilable(::String)) }
|
||||
def blob_sha256_previously_was; end
|
||||
|
||||
sig { returns(T.nilable(::String)) }
|
||||
def blob_sha256_was; end
|
||||
|
||||
sig { void }
|
||||
def blob_sha256_will_change!; end
|
||||
|
||||
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
|
||||
def created_at; end
|
||||
|
||||
@@ -846,6 +918,51 @@ class Domain::PostFile
|
||||
sig { void }
|
||||
def json_attributes_will_change!; end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def last_status_code; end
|
||||
|
||||
sig { params(value: T.nilable(::Integer)).returns(T.nilable(::Integer)) }
|
||||
def last_status_code=(value); end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def last_status_code?; end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def last_status_code_before_last_save; end
|
||||
|
||||
sig { returns(T.untyped) }
|
||||
def last_status_code_before_type_cast; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def last_status_code_came_from_user?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) }
|
||||
def last_status_code_change; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) }
|
||||
def last_status_code_change_to_be_saved; end
|
||||
|
||||
sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) }
|
||||
def last_status_code_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def last_status_code_in_database; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) }
|
||||
def last_status_code_previous_change; end
|
||||
|
||||
sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) }
|
||||
def last_status_code_previously_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def last_status_code_previously_was; end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def last_status_code_was; end
|
||||
|
||||
sig { void }
|
||||
def last_status_code_will_change!; end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def log_entry_id; end
|
||||
|
||||
@@ -936,6 +1053,9 @@ class Domain::PostFile
|
||||
sig { void }
|
||||
def post_id_will_change!; end
|
||||
|
||||
sig { void }
|
||||
def restore_blob_sha256!; end
|
||||
|
||||
sig { void }
|
||||
def restore_created_at!; end
|
||||
|
||||
@@ -951,21 +1071,81 @@ class Domain::PostFile
|
||||
sig { void }
|
||||
def restore_json_attributes!; end
|
||||
|
||||
sig { void }
|
||||
def restore_last_status_code!; end
|
||||
|
||||
sig { void }
|
||||
def restore_log_entry_id!; end
|
||||
|
||||
sig { void }
|
||||
def restore_post_id!; end
|
||||
|
||||
sig { void }
|
||||
def restore_retry_count!; end
|
||||
|
||||
sig { void }
|
||||
def restore_state!; end
|
||||
|
||||
sig { void }
|
||||
def restore_type!; end
|
||||
|
||||
sig { void }
|
||||
def restore_updated_at!; end
|
||||
|
||||
sig { void }
|
||||
def restore_url_str!; end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def retry_count; end
|
||||
|
||||
sig { params(value: T.nilable(::Integer)).returns(T.nilable(::Integer)) }
|
||||
def retry_count=(value); end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def retry_count?; end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def retry_count_before_last_save; end
|
||||
|
||||
sig { returns(T.untyped) }
|
||||
def retry_count_before_type_cast; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def retry_count_came_from_user?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) }
|
||||
def retry_count_change; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) }
|
||||
def retry_count_change_to_be_saved; end
|
||||
|
||||
sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) }
|
||||
def retry_count_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def retry_count_in_database; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) }
|
||||
def retry_count_previous_change; end
|
||||
|
||||
sig { params(from: T.nilable(::Integer), to: T.nilable(::Integer)).returns(T::Boolean) }
|
||||
def retry_count_previously_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def retry_count_previously_was; end
|
||||
|
||||
sig { returns(T.nilable(::Integer)) }
|
||||
def retry_count_was; end
|
||||
|
||||
sig { void }
|
||||
def retry_count_will_change!; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::String), T.nilable(::String)])) }
|
||||
def saved_change_to_blob_sha256; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def saved_change_to_blob_sha256?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
|
||||
def saved_change_to_created_at; end
|
||||
|
||||
@@ -996,6 +1176,12 @@ class Domain::PostFile
|
||||
sig { returns(T::Boolean) }
|
||||
def saved_change_to_json_attributes?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) }
|
||||
def saved_change_to_last_status_code; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def saved_change_to_last_status_code?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) }
|
||||
def saved_change_to_log_entry_id; end
|
||||
|
||||
@@ -1008,12 +1194,24 @@ class Domain::PostFile
|
||||
sig { returns(T::Boolean) }
|
||||
def saved_change_to_post_id?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::Integer), T.nilable(::Integer)])) }
|
||||
def saved_change_to_retry_count; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def saved_change_to_retry_count?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::String), T.nilable(::String)])) }
|
||||
def saved_change_to_state; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def saved_change_to_state?; end
|
||||
|
||||
sig { returns(T.nilable([T.untyped, T.untyped])) }
|
||||
def saved_change_to_type; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def saved_change_to_type?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
|
||||
def saved_change_to_updated_at; end
|
||||
|
||||
@@ -1071,6 +1269,51 @@ class Domain::PostFile
|
||||
sig { void }
|
||||
def state_will_change!; end
|
||||
|
||||
sig { returns(T.untyped) }
|
||||
def type; end
|
||||
|
||||
sig { params(value: T.untyped).returns(T.untyped) }
|
||||
def type=(value); end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def type?; end
|
||||
|
||||
sig { returns(T.untyped) }
|
||||
def type_before_last_save; end
|
||||
|
||||
sig { returns(T.untyped) }
|
||||
def type_before_type_cast; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def type_came_from_user?; end
|
||||
|
||||
sig { returns(T.nilable([T.untyped, T.untyped])) }
|
||||
def type_change; end
|
||||
|
||||
sig { returns(T.nilable([T.untyped, T.untyped])) }
|
||||
def type_change_to_be_saved; end
|
||||
|
||||
sig { params(from: T.untyped, to: T.untyped).returns(T::Boolean) }
|
||||
def type_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
|
||||
sig { returns(T.untyped) }
|
||||
def type_in_database; end
|
||||
|
||||
sig { returns(T.nilable([T.untyped, T.untyped])) }
|
||||
def type_previous_change; end
|
||||
|
||||
sig { params(from: T.untyped, to: T.untyped).returns(T::Boolean) }
|
||||
def type_previously_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
|
||||
sig { returns(T.untyped) }
|
||||
def type_previously_was; end
|
||||
|
||||
sig { returns(T.untyped) }
|
||||
def type_was; end
|
||||
|
||||
sig { void }
|
||||
def type_will_change!; end
|
||||
|
||||
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
|
||||
def updated_at; end
|
||||
|
||||
@@ -1171,6 +1414,9 @@ class Domain::PostFile
|
||||
sig { void }
|
||||
def url_str_will_change!; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_blob_sha256?; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_created_at?; end
|
||||
|
||||
@@ -1186,15 +1432,24 @@ class Domain::PostFile
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_json_attributes?; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_last_status_code?; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_log_entry_id?; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_post_id?; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_retry_count?; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_state?; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_type?; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_updated_at?; end
|
||||
|
||||
|
||||
2277
sorbet/rbi/dsl/domain/post_file/inkbunny_post_file.rbi
generated
Normal file
2277
sorbet/rbi/dsl/domain/post_file/inkbunny_post_file.rbi
generated
Normal file
File diff suppressed because it is too large
Load Diff
67
sorbet/rbi/dsl/domain/user/e621_user.rbi
generated
67
sorbet/rbi/dsl/domain/user/e621_user.rbi
generated
@@ -1175,6 +1175,61 @@ class Domain::User::E621User
|
||||
sig { void }
|
||||
def num_other_favs_cached_will_change!; end
|
||||
|
||||
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
|
||||
def registered_at; end
|
||||
|
||||
sig { params(value: T.nilable(::ActiveSupport::TimeWithZone)).returns(T.nilable(::ActiveSupport::TimeWithZone)) }
|
||||
def registered_at=(value); end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def registered_at?; end
|
||||
|
||||
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
|
||||
def registered_at_before_last_save; end
|
||||
|
||||
sig { returns(T.untyped) }
|
||||
def registered_at_before_type_cast; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def registered_at_came_from_user?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
|
||||
def registered_at_change; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
|
||||
def registered_at_change_to_be_saved; end
|
||||
|
||||
sig do
|
||||
params(
|
||||
from: T.nilable(::ActiveSupport::TimeWithZone),
|
||||
to: T.nilable(::ActiveSupport::TimeWithZone)
|
||||
).returns(T::Boolean)
|
||||
end
|
||||
def registered_at_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
|
||||
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
|
||||
def registered_at_in_database; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
|
||||
def registered_at_previous_change; end
|
||||
|
||||
sig do
|
||||
params(
|
||||
from: T.nilable(::ActiveSupport::TimeWithZone),
|
||||
to: T.nilable(::ActiveSupport::TimeWithZone)
|
||||
).returns(T::Boolean)
|
||||
end
|
||||
def registered_at_previously_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
|
||||
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
|
||||
def registered_at_previously_was; end
|
||||
|
||||
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
|
||||
def registered_at_was; end
|
||||
|
||||
sig { void }
|
||||
def registered_at_will_change!; end
|
||||
|
||||
sig { void }
|
||||
def restore_created_at!; end
|
||||
|
||||
@@ -1202,6 +1257,9 @@ class Domain::User::E621User
|
||||
sig { void }
|
||||
def restore_num_other_favs_cached!; end
|
||||
|
||||
sig { void }
|
||||
def restore_registered_at!; end
|
||||
|
||||
sig { void }
|
||||
def restore_scanned_favs_at!; end
|
||||
|
||||
@@ -1268,6 +1326,12 @@ class Domain::User::E621User
|
||||
sig { returns(T::Boolean) }
|
||||
def saved_change_to_num_other_favs_cached?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
|
||||
def saved_change_to_registered_at; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def saved_change_to_registered_at?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::ActiveSupport::TimeWithZone), T.nilable(::ActiveSupport::TimeWithZone)])) }
|
||||
def saved_change_to_scanned_favs_at; end
|
||||
|
||||
@@ -1519,6 +1583,9 @@ class Domain::User::E621User
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_num_other_favs_cached?; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_registered_at?; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_scanned_favs_at?; end
|
||||
|
||||
|
||||
57
sorbet/rbi/dsl/domain/user/fa_user.rbi
generated
57
sorbet/rbi/dsl/domain/user/fa_user.rbi
generated
@@ -790,6 +790,51 @@ class Domain::User::FaUser
|
||||
end
|
||||
|
||||
module GeneratedAttributeMethods
|
||||
sig { returns(T.nilable(::String)) }
|
||||
def account_status; end
|
||||
|
||||
sig { params(value: T.nilable(::String)).returns(T.nilable(::String)) }
|
||||
def account_status=(value); end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def account_status?; end
|
||||
|
||||
sig { returns(T.nilable(::String)) }
|
||||
def account_status_before_last_save; end
|
||||
|
||||
sig { returns(T.untyped) }
|
||||
def account_status_before_type_cast; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def account_status_came_from_user?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::String), T.nilable(::String)])) }
|
||||
def account_status_change; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::String), T.nilable(::String)])) }
|
||||
def account_status_change_to_be_saved; end
|
||||
|
||||
sig { params(from: T.nilable(::String), to: T.nilable(::String)).returns(T::Boolean) }
|
||||
def account_status_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
|
||||
sig { returns(T.nilable(::String)) }
|
||||
def account_status_in_database; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::String), T.nilable(::String)])) }
|
||||
def account_status_previous_change; end
|
||||
|
||||
sig { params(from: T.nilable(::String), to: T.nilable(::String)).returns(T::Boolean) }
|
||||
def account_status_previously_changed?(from: T.unsafe(nil), to: T.unsafe(nil)); end
|
||||
|
||||
sig { returns(T.nilable(::String)) }
|
||||
def account_status_previously_was; end
|
||||
|
||||
sig { returns(T.nilable(::String)) }
|
||||
def account_status_was; end
|
||||
|
||||
sig { void }
|
||||
def account_status_will_change!; end
|
||||
|
||||
sig { returns(T.nilable(::String)) }
|
||||
def artist_type; end
|
||||
|
||||
@@ -1820,6 +1865,9 @@ class Domain::User::FaUser
|
||||
sig { void }
|
||||
def registered_at_will_change!; end
|
||||
|
||||
sig { void }
|
||||
def restore_account_status!; end
|
||||
|
||||
sig { void }
|
||||
def restore_artist_type!; end
|
||||
|
||||
@@ -1913,6 +1961,12 @@ class Domain::User::FaUser
|
||||
sig { void }
|
||||
def restore_url_name!; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::String), T.nilable(::String)])) }
|
||||
def saved_change_to_account_status; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def saved_change_to_account_status?; end
|
||||
|
||||
sig { returns(T.nilable([T.nilable(::String), T.nilable(::String)])) }
|
||||
def saved_change_to_artist_type; end
|
||||
|
||||
@@ -2564,6 +2618,9 @@ class Domain::User::FaUser
|
||||
sig { void }
|
||||
def url_name_will_change!; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_account_status?; end
|
||||
|
||||
sig { returns(T::Boolean) }
|
||||
def will_save_change_to_artist_type?; end
|
||||
|
||||
|
||||
10
sorbet/rbi/dsl/domain/user/inkbunny_user.rbi
generated
10
sorbet/rbi/dsl/domain/user/inkbunny_user.rbi
generated
@@ -513,12 +513,12 @@ class Domain::User::InkbunnyUser
|
||||
sig { params(ids: T::Array[T.untyped]).returns(T::Array[T.untyped]) }
|
||||
def faved_post_ids=(ids); end
|
||||
|
||||
# This method is created by ActiveRecord on the `Domain::User` class because it declared `has_many :faved_posts, through: :user_post_favs`.
|
||||
# This method is created by ActiveRecord on the `Domain::User::InkbunnyUser` class because it declared `has_many :faved_posts, through: :user_post_favs`.
|
||||
# 🔗 [Rails guide for `has_many_through` association](https://guides.rubyonrails.org/association_basics.html#the-has-many-through-association)
|
||||
sig { returns(::Domain::Post::PrivateCollectionProxy) }
|
||||
sig { returns(::Domain::Post::InkbunnyPost::PrivateCollectionProxy) }
|
||||
def faved_posts; end
|
||||
|
||||
sig { params(value: T::Enumerable[::Domain::Post]).void }
|
||||
sig { params(value: T::Enumerable[::Domain::Post::InkbunnyPost]).void }
|
||||
def faved_posts=(value); end
|
||||
|
||||
sig { returns(T::Array[T.untyped]) }
|
||||
@@ -557,10 +557,10 @@ class Domain::User::InkbunnyUser
|
||||
|
||||
# This method is created by ActiveRecord on the `Domain::User::InkbunnyUser` class because it declared `has_many :posts, through: :user_post_creations`.
|
||||
# 🔗 [Rails guide for `has_many_through` association](https://guides.rubyonrails.org/association_basics.html#the-has-many-through-association)
|
||||
sig { returns(::Domain::Inkbunny::Post::PrivateCollectionProxy) }
|
||||
sig { returns(::Domain::Post::InkbunnyPost::PrivateCollectionProxy) }
|
||||
def posts; end
|
||||
|
||||
sig { params(value: T::Enumerable[::Domain::Inkbunny::Post]).void }
|
||||
sig { params(value: T::Enumerable[::Domain::Post::InkbunnyPost]).void }
|
||||
def posts=(value); end
|
||||
|
||||
sig { returns(T.nilable(::Domain::UserAvatar)) }
|
||||
|
||||
12
sorbet/rbi/dsl/generated_path_helpers_module.rbi
generated
12
sorbet/rbi/dsl/generated_path_helpers_module.rbi
generated
@@ -60,6 +60,18 @@ module GeneratedPathHelpersModule
|
||||
sig { params(args: T.untyped).returns(String) }
|
||||
def domain_inkbunny_user_posts_path(*args); end
|
||||
|
||||
sig { params(args: T.untyped).returns(String) }
|
||||
def domain_post_path(*args); end
|
||||
|
||||
sig { params(args: T.untyped).returns(String) }
|
||||
def domain_posts_path(*args); end
|
||||
|
||||
sig { params(args: T.untyped).returns(String) }
|
||||
def domain_user_path(*args); end
|
||||
|
||||
sig { params(args: T.untyped).returns(String) }
|
||||
def domain_user_posts_path(*args); end
|
||||
|
||||
sig { params(args: T.untyped).returns(String) }
|
||||
def edit_global_state_path(*args); end
|
||||
|
||||
|
||||
12
sorbet/rbi/dsl/generated_url_helpers_module.rbi
generated
12
sorbet/rbi/dsl/generated_url_helpers_module.rbi
generated
@@ -60,6 +60,18 @@ module GeneratedUrlHelpersModule
|
||||
sig { params(args: T.untyped).returns(String) }
|
||||
def domain_inkbunny_user_url(*args); end
|
||||
|
||||
sig { params(args: T.untyped).returns(String) }
|
||||
def domain_post_url(*args); end
|
||||
|
||||
sig { params(args: T.untyped).returns(String) }
|
||||
def domain_posts_url(*args); end
|
||||
|
||||
sig { params(args: T.untyped).returns(String) }
|
||||
def domain_user_posts_url(*args); end
|
||||
|
||||
sig { params(args: T.untyped).returns(String) }
|
||||
def domain_user_url(*args); end
|
||||
|
||||
sig { params(args: T.untyped).returns(String) }
|
||||
def edit_global_state_url(*args); end
|
||||
|
||||
|
||||
3
sorbet/rbi/dsl/rails/application_controller.rbi
generated
3
sorbet/rbi/dsl/rails/application_controller.rbi
generated
@@ -32,6 +32,9 @@ class Rails::ApplicationController
|
||||
include ::Domain::E621::PostsHelper
|
||||
include ::Domain::Fa::PostsHelper
|
||||
include ::Domain::Fa::UsersHelper
|
||||
include ::HelpersInterface
|
||||
include ::Domain::PostsHelper
|
||||
include ::Domain::UsersHelper
|
||||
include ::GoodJobHelper
|
||||
include ::IndexablePostsHelper
|
||||
include ::LogEntriesHelper
|
||||
|
||||
@@ -32,6 +32,9 @@ class Rails::Conductor::BaseController
|
||||
include ::Domain::E621::PostsHelper
|
||||
include ::Domain::Fa::PostsHelper
|
||||
include ::Domain::Fa::UsersHelper
|
||||
include ::HelpersInterface
|
||||
include ::Domain::PostsHelper
|
||||
include ::Domain::UsersHelper
|
||||
include ::GoodJobHelper
|
||||
include ::IndexablePostsHelper
|
||||
include ::LogEntriesHelper
|
||||
|
||||
3
sorbet/rbi/dsl/rails/health_controller.rbi
generated
3
sorbet/rbi/dsl/rails/health_controller.rbi
generated
@@ -32,6 +32,9 @@ class Rails::HealthController
|
||||
include ::Domain::E621::PostsHelper
|
||||
include ::Domain::Fa::PostsHelper
|
||||
include ::Domain::Fa::UsersHelper
|
||||
include ::HelpersInterface
|
||||
include ::Domain::PostsHelper
|
||||
include ::Domain::UsersHelper
|
||||
include ::GoodJobHelper
|
||||
include ::IndexablePostsHelper
|
||||
include ::LogEntriesHelper
|
||||
|
||||
@@ -337,8 +337,7 @@ describe Domain::Fa::Job::BrowsePageJob do
|
||||
before do
|
||||
post = find_post.call
|
||||
post.scanned_at = 1.hour.ago
|
||||
file = post.build_file(url_str: "http://www.example.com/img.jpg")
|
||||
file.save!
|
||||
post.files.create!(url_str: "http://www.example.com/img.jpg")
|
||||
post.save!
|
||||
perform_now({})
|
||||
end
|
||||
|
||||
181
spec/jobs/domain/fa/job/scan_file_job_spec.rb
Normal file
181
spec/jobs/domain/fa/job/scan_file_job_spec.rb
Normal file
@@ -0,0 +1,181 @@
|
||||
# typed: false
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe Domain::Fa::Job::ScanFileJob do
|
||||
include PerformJobHelpers
|
||||
|
||||
let(:fa_post) { create(:domain_post_fa_post) }
|
||||
let(:post_file) { create(:domain_post_file) }
|
||||
let(:http_client_mock) { instance_double("::Scraper::HttpClient") }
|
||||
|
||||
before do
|
||||
Scraper::ClientFactory.http_client_mock = http_client_mock
|
||||
@log_entries =
|
||||
HttpClientMockHelpers.init_http_client_mock(
|
||||
http_client_mock,
|
||||
client_mock_config,
|
||||
)
|
||||
end
|
||||
|
||||
describe "#perform" do
|
||||
context "with post_file arg" do
|
||||
context "with 200 response" do
|
||||
let(:client_mock_config) do
|
||||
[
|
||||
{
|
||||
uri: post_file.url_str,
|
||||
status_code: 200,
|
||||
content_type: "image/jpeg",
|
||||
contents: "fake image data",
|
||||
},
|
||||
]
|
||||
end
|
||||
|
||||
it "processes a file successfully" do
|
||||
post_file.update!(state: "pending")
|
||||
perform_now({ post_file: post_file })
|
||||
|
||||
post_file.reload
|
||||
expect(post_file.state).to eq("ok")
|
||||
expect(post_file.retry_count).to eq(0)
|
||||
end
|
||||
end
|
||||
|
||||
context "with 404 response" do
|
||||
let(:client_mock_config) do
|
||||
[
|
||||
{
|
||||
uri: post_file.url_str,
|
||||
status_code: 404,
|
||||
content_type: "text/html",
|
||||
contents: "not found",
|
||||
},
|
||||
]
|
||||
end
|
||||
|
||||
it "handles 404 response" do
|
||||
post_file.update!(state: "pending")
|
||||
perform_now({ post_file: post_file })
|
||||
|
||||
post_file.reload
|
||||
expect(post_file.state).to eq("terminal_error")
|
||||
expect(post_file.retry_count).to eq(0)
|
||||
end
|
||||
end
|
||||
|
||||
context "with 500 response" do
|
||||
let(:client_mock_config) do
|
||||
[
|
||||
{
|
||||
uri: post_file.url_str,
|
||||
status_code: 500,
|
||||
content_type: "text/html",
|
||||
contents: "server error",
|
||||
},
|
||||
]
|
||||
end
|
||||
|
||||
it "handles other error responses" do
|
||||
post_file.update!(state: "pending")
|
||||
perform_now(
|
||||
{ post_file: post_file },
|
||||
should_raise: /response 500, aborting/,
|
||||
)
|
||||
|
||||
post_file.reload
|
||||
expect(post_file.state).to eq("retryable_error")
|
||||
expect(post_file.retry_count).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
context "with existing state" do
|
||||
let(:client_mock_config) { [] }
|
||||
|
||||
it "skips processing when state is ok" do
|
||||
post_file.update!(state: "ok")
|
||||
|
||||
perform_now({ post_file: post_file })
|
||||
|
||||
post_file.reload
|
||||
expect(post_file.state).to eq("ok")
|
||||
end
|
||||
|
||||
it "skips processing when state is terminal_error" do
|
||||
post_file.update!(state: "terminal_error")
|
||||
|
||||
perform_now({ post_file: post_file })
|
||||
|
||||
post_file.reload
|
||||
expect(post_file.state).to eq("terminal_error")
|
||||
end
|
||||
|
||||
it "skips processing when retry_count >= 3 for retryable_error" do
|
||||
post_file.update!(state: "retryable_error", retry_count: 3)
|
||||
|
||||
perform_now({ post_file: post_file })
|
||||
|
||||
post_file.reload
|
||||
expect(post_file.state).to eq("retryable_error")
|
||||
expect(post_file.retry_count).to eq(3)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "with post arg" do
|
||||
let(:post) { create(:domain_post_fa_post) }
|
||||
|
||||
context "with valid post file" do
|
||||
let(:post_file) { create(:domain_post_file, post: post) }
|
||||
let(:client_mock_config) do
|
||||
[
|
||||
{
|
||||
uri: post_file.url_str,
|
||||
status_code: 200,
|
||||
content_type: "image/jpeg",
|
||||
contents: "fake image data",
|
||||
},
|
||||
]
|
||||
end
|
||||
|
||||
it "processes using the post's file" do
|
||||
post_file.update!(state: "pending")
|
||||
perform_now({ post: post })
|
||||
|
||||
post_file.reload
|
||||
expect(post_file.state).to eq("ok")
|
||||
end
|
||||
end
|
||||
|
||||
context "with missing post" do
|
||||
let(:client_mock_config) { [] }
|
||||
|
||||
it "enqueues a scan post job when post not found using fa_id" do
|
||||
perform_now({ fa_id: "12345" })
|
||||
|
||||
expect(
|
||||
SpecUtil.enqueued_job_args(Domain::Fa::Job::ScanPostJob),
|
||||
).to include(hash_including(fa_id: "12345"))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "error handling" do
|
||||
let(:client_mock_config) { [] }
|
||||
|
||||
it "raises error for invalid post type" do
|
||||
invalid_post = create(:domain_e621_post)
|
||||
|
||||
perform_now(
|
||||
{ post: invalid_post },
|
||||
should_raise: /invalid post model: Domain::E621::Post/,
|
||||
)
|
||||
end
|
||||
|
||||
it "raises error when file has no url" do
|
||||
post_file.update!(url_str: nil, state: "pending")
|
||||
|
||||
perform_now({ post_file: post_file }, should_raise: /file has no url/)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -28,6 +28,7 @@ RSpec.describe Domain::MigrateToDomain do
|
||||
sources_array: old_post.sources_array,
|
||||
artists_array: old_post.artists_array,
|
||||
e621_updated_at: be_within(1.second).of(old_post.e621_updated_at),
|
||||
posted_at: be_within(1.second).of(old_post.posted_at),
|
||||
last_index_page_id: old_post.last_index_page_id,
|
||||
caused_by_entry_id: old_post.caused_by_entry_id,
|
||||
scan_log_entry_id: old_post.scan_log_entry_id,
|
||||
@@ -133,9 +134,83 @@ RSpec.describe Domain::MigrateToDomain do
|
||||
file_error: nil,
|
||||
parent_e621_id: nil,
|
||||
scanned_post_favs_at: Time.current,
|
||||
posted_at: 2.days.ago,
|
||||
)
|
||||
end
|
||||
|
||||
# Add new test cases for file handling
|
||||
context "when handling post files" do
|
||||
let(:file_url) { "https://example.com/image.jpg" }
|
||||
let(:log_entry) do
|
||||
create(:http_log_entry, uri_str: "https://example.com/image.jpg")
|
||||
end
|
||||
|
||||
it "creates a file with state 'ok' when file exists with status 200" do
|
||||
old_post.file = log_entry
|
||||
old_post.file_url_str = file_url
|
||||
old_post.save!
|
||||
|
||||
migrator.migrate_e621_posts
|
||||
|
||||
new_post = Domain::Post::E621Post.find_by(e621_id: old_post.e621_id)
|
||||
expect(new_post.file).to have_attributes(
|
||||
url_str: file_url,
|
||||
log_entry_id: log_entry.id,
|
||||
state: "ok",
|
||||
)
|
||||
end
|
||||
|
||||
it "creates a file with state 'terminal_error' when file exists with non-200 status" do
|
||||
error_log_entry =
|
||||
create(
|
||||
:http_log_entry,
|
||||
:with_error,
|
||||
uri_str: "https://example.com/image.jpg",
|
||||
)
|
||||
|
||||
old_post.update!(
|
||||
file: error_log_entry,
|
||||
file_url_str: file_url,
|
||||
file_error:
|
||||
Domain::E621::Post::FileError.new(
|
||||
status_code: 404,
|
||||
log_entry_id: error_log_entry.id,
|
||||
retry_count: 1,
|
||||
),
|
||||
)
|
||||
|
||||
migrator.migrate_e621_posts
|
||||
|
||||
new_post = Domain::Post::E621Post.find_by(e621_id: old_post.e621_id)
|
||||
expect(new_post.file).to have_attributes(
|
||||
url_str: file_url,
|
||||
log_entry_id: error_log_entry.id,
|
||||
state: "terminal_error",
|
||||
error_message: "status_code: 404",
|
||||
)
|
||||
end
|
||||
|
||||
it "creates a file with state 'pending' when only file_url_str exists" do
|
||||
old_post.update!(file_url_str: file_url)
|
||||
|
||||
migrator.migrate_e621_posts
|
||||
|
||||
new_post = Domain::Post::E621Post.find_by(e621_id: old_post.e621_id)
|
||||
expect(new_post.file).to have_attributes(
|
||||
url_str: file_url,
|
||||
log_entry_id: nil,
|
||||
state: "pending",
|
||||
)
|
||||
end
|
||||
|
||||
it "does not create a file when neither file nor file_url_str exists" do
|
||||
migrator.migrate_e621_posts
|
||||
|
||||
new_post = Domain::Post::E621Post.find_by(e621_id: old_post.e621_id)
|
||||
expect(new_post.file).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
it "migrates posts that don't exist in the new table" do
|
||||
expect { migrator.migrate_e621_posts }.to change(
|
||||
Domain::Post::E621Post,
|
||||
@@ -185,6 +260,7 @@ RSpec.describe Domain::MigrateToDomain do
|
||||
file_error: nil,
|
||||
parent_e621_id: nil,
|
||||
scanned_post_favs_at: Time.current,
|
||||
posted_at: 2.days.ago,
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
@@ -193,6 +193,17 @@ RSpec.describe Domain::MigrateToDomain do
|
||||
end
|
||||
end
|
||||
|
||||
it "handles disabled users" do
|
||||
old_user.state = "scan_error"
|
||||
old_user.state_detail = {
|
||||
"scan_error" =>
|
||||
"account disabled or not found, see last_scanned_page_id",
|
||||
}
|
||||
old_user.save!
|
||||
|
||||
migrator.migrate_fa_users
|
||||
end
|
||||
|
||||
it "handles users with avatars" do
|
||||
avatar = create(:domain_fa_user_avatar, user: old_user)
|
||||
old_user.avatar = avatar
|
||||
|
||||
@@ -61,19 +61,14 @@ RSpec.describe Domain::MigrateToDomain do
|
||||
expect(new_post.creator).to be_nil
|
||||
end
|
||||
|
||||
if old_post.files.present?
|
||||
expect(new_post.files.map(&:log_entry_id)).to match_array(
|
||||
old_post.files.map(&:log_entry_id),
|
||||
)
|
||||
expect(new_post.files.map(&:url_str)).to match_array(
|
||||
old_post.files.map(&:url_str),
|
||||
)
|
||||
expect(new_post.files.map(&:state)).to match_array(
|
||||
old_post.files.map(&:state),
|
||||
)
|
||||
else
|
||||
expect(new_post.files).to be_empty
|
||||
end
|
||||
new_post
|
||||
.files
|
||||
.zip(old_post.files)
|
||||
.each do |new_file, old_file|
|
||||
expect_inkbunny_files_match(new_file, old_file)
|
||||
end
|
||||
|
||||
expect(new_post.files).to be_empty if old_post.files.empty?
|
||||
end
|
||||
|
||||
def expect_inkbunny_pools_match(old_pool, new_pool)
|
||||
@@ -106,6 +101,34 @@ RSpec.describe Domain::MigrateToDomain do
|
||||
end
|
||||
end
|
||||
|
||||
def expect_inkbunny_files_match(new_file, old_file)
|
||||
new_state =
|
||||
case old_file.state
|
||||
when "ok"
|
||||
old_file.log_entry_id.present? ? "ok" : "pending"
|
||||
else
|
||||
"terminal_error"
|
||||
end
|
||||
|
||||
expect(new_file).to have_attributes(
|
||||
ib_id: old_file.ib_file_id,
|
||||
file_name: old_file.file_name,
|
||||
url_str: old_file.url_str,
|
||||
file_order: old_file.file_order,
|
||||
md5_initial: old_file.md5_initial,
|
||||
md5_full: old_file.md5_full,
|
||||
md5s: old_file.md5s,
|
||||
state: new_state,
|
||||
log_entry_id: old_file.log_entry_id,
|
||||
last_status_code: old_file.log_entry&.status_code,
|
||||
)
|
||||
|
||||
expect(new_file.ib_created_at).to match_presence_or_be_within(
|
||||
1.second,
|
||||
of: old_file.ib_created_at,
|
||||
)
|
||||
end
|
||||
|
||||
describe "#migrate_inkbunny_users" do
|
||||
let!(:old_user) do
|
||||
create(
|
||||
@@ -283,14 +306,9 @@ RSpec.describe Domain::MigrateToDomain do
|
||||
)
|
||||
end
|
||||
|
||||
let!(:old_post) do
|
||||
post = create(:domain_inkbunny_post, creator: creator)
|
||||
file = create(:domain_inkbunny_file, post: post)
|
||||
post.files << file
|
||||
post
|
||||
end
|
||||
|
||||
it "migrates posts that don't exist in the new table" do
|
||||
old_post = create(:domain_inkbunny_post, creator: creator)
|
||||
|
||||
expect { migrator.migrate_inkbunny_posts }.to change(
|
||||
Domain::Post::InkbunnyPost,
|
||||
:count,
|
||||
@@ -301,6 +319,8 @@ RSpec.describe Domain::MigrateToDomain do
|
||||
end
|
||||
|
||||
it "skips posts that already exist in the new table" do
|
||||
old_post = create(:domain_inkbunny_post, creator: creator)
|
||||
|
||||
# Create a post in the new table first
|
||||
Domain::Post::InkbunnyPost.create!(
|
||||
ib_id: old_post.ib_post_id,
|
||||
@@ -316,9 +336,9 @@ RSpec.describe Domain::MigrateToDomain do
|
||||
end
|
||||
|
||||
it "handles multiple posts in batches" do
|
||||
# Create a few more old posts
|
||||
additional_posts =
|
||||
2.times.map do |i|
|
||||
# Create a few old posts
|
||||
old_posts =
|
||||
3.times.map do |i|
|
||||
post = create(:domain_inkbunny_post, creator: creator)
|
||||
file = create(:domain_inkbunny_file, post: post)
|
||||
post.files << file
|
||||
@@ -332,13 +352,11 @@ RSpec.describe Domain::MigrateToDomain do
|
||||
|
||||
expect(Domain::Post::InkbunnyPost.count).to eq(3)
|
||||
expect(Domain::Post::InkbunnyPost.pluck(:ib_id)).to contain_exactly(
|
||||
old_post.ib_post_id,
|
||||
additional_posts[0].ib_post_id,
|
||||
additional_posts[1].ib_post_id,
|
||||
*old_posts.map(&:ib_post_id),
|
||||
)
|
||||
|
||||
# Verify all posts were migrated correctly
|
||||
([old_post] + additional_posts).each do |old_post|
|
||||
old_posts.each do |old_post|
|
||||
new_post =
|
||||
Domain::Post::InkbunnyPost.find_by(ib_id: old_post.ib_post_id)
|
||||
expect_inkbunny_posts_match(old_post, new_post)
|
||||
@@ -360,12 +378,111 @@ RSpec.describe Domain::MigrateToDomain do
|
||||
expect { migrator.migrate_inkbunny_posts }.to change(
|
||||
Domain::Post::InkbunnyPost,
|
||||
:count,
|
||||
).by(2)
|
||||
).by(1)
|
||||
|
||||
new_post =
|
||||
Domain::Post::InkbunnyPost.find_by(ib_id: post_without_files.ib_post_id)
|
||||
expect_inkbunny_posts_match(post_without_files, new_post)
|
||||
end
|
||||
|
||||
it "handles posts with files and verifies file migration" do
|
||||
old_post = create(:domain_inkbunny_post, creator: creator)
|
||||
old_file =
|
||||
create(
|
||||
:domain_inkbunny_file,
|
||||
post: old_post,
|
||||
ib_file_id: 456,
|
||||
file_name: "test.jpg",
|
||||
url_str: "https://example.com/test.jpg",
|
||||
ib_created_at: Time.current,
|
||||
file_order: 1,
|
||||
md5_initial: "abc123",
|
||||
md5_full: "abc123_full",
|
||||
md5s: ["abc123"],
|
||||
state: :ok,
|
||||
state_detail: {
|
||||
},
|
||||
log_entry: create(:http_log_entry),
|
||||
blob_entry: create(:blob_entry),
|
||||
)
|
||||
|
||||
expect { migrator.migrate_inkbunny_posts }.to change(
|
||||
Domain::Post::InkbunnyPost,
|
||||
:count,
|
||||
).by(1).and change(Domain::PostFile::InkbunnyPostFile, :count).by(1)
|
||||
|
||||
new_post = Domain::Post::InkbunnyPost.find_by(ib_id: old_post.ib_post_id)
|
||||
expect(new_post.files.count).to eq(1)
|
||||
|
||||
new_file = new_post.files.first
|
||||
expect_inkbunny_files_match(new_file, old_file)
|
||||
end
|
||||
|
||||
it "handles posts with multiple files" do
|
||||
old_post = create(:domain_inkbunny_post, creator: creator)
|
||||
old_files =
|
||||
3.times.map do |i|
|
||||
create(
|
||||
:domain_inkbunny_file,
|
||||
post: old_post,
|
||||
ib_file_id: 789 + i,
|
||||
file_name: "test#{i}.jpg",
|
||||
url_str: "https://example.com/test#{i}.jpg",
|
||||
ib_created_at: Time.current,
|
||||
file_order: i + 1,
|
||||
md5_initial: "def#{i}456",
|
||||
md5_full: "def#{i}456_full",
|
||||
md5s: ["def#{i}456"],
|
||||
state: :ok,
|
||||
state_detail: {
|
||||
},
|
||||
log_entry: create(:http_log_entry),
|
||||
blob_entry: create(:blob_entry),
|
||||
)
|
||||
end
|
||||
|
||||
expect { migrator.migrate_inkbunny_posts }.to change(
|
||||
Domain::Post::InkbunnyPost,
|
||||
:count,
|
||||
).by(1).and change(Domain::PostFile::InkbunnyPostFile, :count).by(3)
|
||||
|
||||
new_post = Domain::Post::InkbunnyPost.find_by(ib_id: old_post.ib_post_id)
|
||||
expect(new_post.files.count).to eq(3)
|
||||
expect(new_post.files.map(&:ib_id)).to contain_exactly(789, 790, 791)
|
||||
|
||||
old_files.each do |old_file|
|
||||
new_file = new_post.files.find { |f| f.ib_id == old_file.ib_file_id }
|
||||
expect_inkbunny_files_match(new_file, old_file)
|
||||
end
|
||||
end
|
||||
|
||||
it "handles posts with files in error states" do
|
||||
old_post = create(:domain_inkbunny_post, creator: creator)
|
||||
old_file =
|
||||
create(
|
||||
:domain_inkbunny_file,
|
||||
post: old_post,
|
||||
ib_file_id: 999,
|
||||
file_name: "error.jpg",
|
||||
url_str: "https://example.com/error.jpg",
|
||||
ib_created_at: Time.current,
|
||||
file_order: 1,
|
||||
state: :error,
|
||||
state_detail: {
|
||||
"error" => "download failed",
|
||||
},
|
||||
log_entry: create(:http_log_entry),
|
||||
)
|
||||
|
||||
expect { migrator.migrate_inkbunny_posts }.to change(
|
||||
Domain::Post::InkbunnyPost,
|
||||
:count,
|
||||
).by(1).and change(Domain::PostFile::InkbunnyPostFile, :count).by(1)
|
||||
|
||||
new_post = Domain::Post::InkbunnyPost.find_by(ib_id: old_post.ib_post_id)
|
||||
new_file = new_post.files.first
|
||||
expect_inkbunny_files_match(new_file, old_file)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#migrate_inkbunny_pools" do
|
||||
|
||||
Reference in New Issue
Block a user