fa user and post controller cleanup

This commit is contained in:
Dylan Knutson
2023-05-21 18:52:35 -07:00
parent cb2270aab0
commit 63994d5a62
17 changed files with 201 additions and 156 deletions

View File

@@ -62,6 +62,7 @@ group :development do
# Use console on exceptions pages [https://github.com/rails/web-console]
gem "web-console"
gem "htmlbeautifier"
gem "rails_live_reload"
# Speed up commands on slow machines / big apps [https://github.com/rails/spring]
# gem "spring"
@@ -107,6 +108,8 @@ gem "concurrent-ruby-ext", require: "concurrent"
gem "concurrent-ruby-edge", require: "concurrent-edge"
gem "pluck_each"
gem "good_job"
gem "zstd-ruby"
gem "rszr"
gem "pg_query", ">= 2"
gem "pghero", git: "https://github.com/dymk/pghero", ref: "e314f99"
@@ -124,8 +127,6 @@ group :production do
gem "sd_notify"
end
# gem "pg_party"
gem "tailwindcss-rails", "~> 2.0"
gem "shakapacker"

View File

@@ -174,6 +174,9 @@ GEM
kaminari-core (1.2.2)
libmf (0.3.0)
ffi
listen (3.8.0)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
loofah (2.19.1)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
@@ -252,6 +255,11 @@ GEM
nokogiri (>= 1.6)
rails-html-sanitizer (1.5.0)
loofah (~> 2.19, >= 2.19.1)
rails_live_reload (0.3.4)
listen
nio4r
railties
websocket-driver
rails_semantic_logger (4.12.0)
rack
railties (>= 5.1)
@@ -265,6 +273,9 @@ GEM
zeitwerk (~> 2.5)
rainbow (3.1.1)
rake (13.0.6)
rb-fsevent (0.11.2)
rb-inotify (0.10.1)
ffi (~> 1.0)
react_on_rails (13.3.3)
addressable
connection_pool
@@ -294,6 +305,7 @@ GEM
rspec-mocks (~> 3.11)
rspec-support (~> 3.11)
rspec-support (3.12.0)
rszr (1.3.0)
ruby-prof (1.4.5)
ruby-prof-speedscope (0.3.0)
ruby-prof (~> 1.0)
@@ -355,6 +367,7 @@ GEM
xpath (3.2.0)
nokogiri (~> 1.8)
zeitwerk (2.6.6)
zstd-ruby (1.5.5.0)
PLATFORMS
ruby
@@ -393,11 +406,13 @@ DEPENDENCIES
rack-cors
rack-mini-profiler
rails (~> 7.0.4, >= 7.0.4.2)
rails_live_reload
rails_semantic_logger
rb-bsdiff!
react_on_rails
ripcord
rspec-rails
rszr
ruby-prof
ruby-prof-speedscope
rufo
@@ -415,6 +430,7 @@ DEPENDENCIES
web-console
webdrivers
xdiff!
zstd-ruby
RUBY VERSION
ruby 3.2.0p0

View File

@@ -1,5 +1,6 @@
# Procfile for development using HMR
# You can run these commands in separate shells
rails: bundle exec rails s -p 3000
tailwind: bundle exec rake tailwindcss:watch
wp-client: HMR=true bin/webpacker-dev-server
wp-server: HMR=true SERVER_BUNDLE_ONLY=yes bin/webpacker --watch
wp-server: HMR=true SERVER_BUNDLE_ONLY=yes bin/webpacker --watch

View File

@@ -77,16 +77,18 @@ end
namespace :db_sampler do
task :export => :environment do
url_names = ENV["url_names"] || raise("need 'url_names' (comma-separated)")
outfile_path = ENV["outfile"] || raise("need 'outfile' (file path)")
outfile = File.open(outfile_path, "wb")
# outfile_path = ENV["outfile"] || raise("need 'outfile' (file path)")
# outfile = File.open(outfile_path, "wb")
outfile = $stdout
DbSampler.new(outfile).export(url_names.split(","))
ensure
outfile.close if outfile
end
task :import => [:environment] do
infile_path = ENV["infile"] || raise("need 'infile' (file path)")
infile = File.open(infile_path, "rb")
# infile_path = ENV["infile"] || raise("need 'infile' (file path)")
# infile = File.open(infile_path, "rb")
infile = $stdin
DbSampler.new(infile).import
ensure
infile.close if infile

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M13.5 6H5.25A2.25 2.25 0 003 8.25v10.5A2.25 2.25 0 005.25 21h10.5A2.25 2.25 0 0018 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" />
</svg>

After

Width:  |  Height:  |  Size: 326 B

View File

@@ -0,0 +1,6 @@
class BlobsController
def show
sha256 = HexUtil.hex2bin(params[:sha256])
@blob = BlobEntryP.ensure(sha256: sha256)
end
end

View File

@@ -7,7 +7,11 @@ class Domain::Fa::PostsController < ApplicationController
# GET /domain/fa/posts
def index
@posts = Domain::Fa::Post.
if params[:user_url_name]
@user = Domain::Fa::User.find_by(url_name: params[:user_url_name]) || raise("404")
end
relation = @user ? @user.posts : Domain::Fa::Post
@posts = relation.
includes(:creator, :file).
page(params[:page]).
per(50).

View File

@@ -1,71 +1,19 @@
class Domain::Fa::UsersController < ApplicationController
before_action :set_domain_fa_user, only: %i[ show edit update destroy ]
before_action :set_user, only: %i[ show ]
# GET /domain/fa/users or /domain/fa/users.json
def index
@domain_fa_users = Domain::Fa::User.page(params[:page])
@users = Domain::Fa::User.includes({ avatar: [:file] }).page(params[:page])
end
# GET /domain/fa/users/1 or /domain/fa/users/1.json
def show
end
# GET /domain/fa/users/new
def new
@domain_fa_user = Domain::Fa::User.new
end
# GET /domain/fa/users/1/edit
def edit
end
# POST /domain/fa/users or /domain/fa/users.json
def create
@domain_fa_user = Domain::Fa::User.new(domain_fa_user_params)
respond_to do |format|
if @domain_fa_user.save
format.html { redirect_to domain_fa_user_url(@domain_fa_user), notice: "User was successfully created." }
format.json { render :show, status: :created, location: @domain_fa_user }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: @domain_fa_user.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /domain/fa/users/1 or /domain/fa/users/1.json
def update
respond_to do |format|
if @domain_fa_user.update(domain_fa_user_params)
format.html { redirect_to domain_fa_user_url(@domain_fa_user), notice: "User was successfully updated." }
format.json { render :show, status: :ok, location: @domain_fa_user }
else
format.html { render :edit, status: :unprocessable_entity }
format.json { render json: @domain_fa_user.errors, status: :unprocessable_entity }
end
end
end
# DELETE /domain/fa/users/1 or /domain/fa/users/1.json
def destroy
@domain_fa_user.destroy
respond_to do |format|
format.html { redirect_to domain_fa_users_url, notice: "User was successfully destroyed." }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_domain_fa_user
@domain_fa_user = Domain::Fa::User.find(params[:id])
end
# Only allow a list of trusted parameters through.
def domain_fa_user_params
params.fetch(:domain_fa_user, {})
def set_user
@user = Domain::Fa::User.find_by(url_name: params[:url_name])
end
end

View File

@@ -83,20 +83,42 @@ class LogEntriesController < ApplicationController
expires_dur = 1.year
response.headers["Expires"] = expires_dur.from_now.httpdate
expires_in expires_dur, public: true
thumb = params[:thumb]
raise("invalid thumb #{thumb}") if !thumb.blank? && !thumb_params(thumb)
log_entry = HttpLogEntry.find(params[:id])
hex_sha256 = HexUtil.bin2hex(log_entry.response_sha256)
return unless stale?(last_modified: Time.at(0), strong_etag: hex_sha256)
etag = HexUtil.bin2hex(log_entry.response_sha256)
etag += "-#{thumb}" if thumb
return unless stale?(last_modified: Time.at(0), strong_etag: etag)
# images, videos, etc
entry_response = log_entry.response
if helpers.is_send_data_content_type?(entry_response.content_type)
send_data(
entry_response.contents,
type: entry_response.content_type,
disposition: "inline",
filename: log_entry.uri.path,
)
if !thumb.blank? && helpers.is_thumbable_content_type?(entry_response.content_type)
filename = "thumb-#{thumb}-#{log_entry.uri.path}"
filename = filename[..File.extname(filename).length]
filename += ".jpeg"
file = Tempfile.new("thumb.jpeg", binmode: true)
file.write(entry_response.contents)
image = Rszr::Image.load(file.path)
image.resize!(*thumb_params(thumb))
resized_image_contents = image.save_data(format: :jpeg)
send_data(
resized_image_contents,
type: "image/jpeg",
disposition: "inline",
filename: filename,
)
else
send_data(
entry_response.contents,
type: entry_response.content_type,
disposition: "inline",
filename: log_entry.uri.path,
)
end
elsif entry_response.content_type =~ /text\/plain/
render plain: entry_response.contents
elsif entry_response.content_type.starts_with? "text/html"
@@ -108,4 +130,15 @@ class LogEntriesController < ApplicationController
render plain: "no renderer for #{entry_response.content_type}"
end
end
private
def thumb_params(thumb)
case thumb
when "small"
[400, 300]
when "medium"
[800, 600]
end
end
end

View File

@@ -14,6 +14,10 @@ module LogEntriesHelper
].any? { |ct| content_type.starts_with?(ct) }
end
def is_thumbable_content_type?(content_type)
is_renderable_image_type?(content_type)
end
def is_renderable_video_type?(content_type)
[
"video/mp4",

View File

@@ -1,7 +1,4 @@
class DbSampler
include HasColorLogger
include HasMeasureDuration
SCHEMA = {
::Domain::Fa::User => [
:avatar,
@@ -41,22 +38,27 @@ class DbSampler
end
def import
puts "reading file..."
$stderr.puts "reading file..."
deferred = []
while (line = @file.gets)
line.chomp!
model = Marshal.load(Base64.strict_decode64(line))
model = Marshal.load(Zstd.decompress(Base64.strict_decode64(line)))
begin
import_model(model)
rescue ActiveRecord::InvalidForeignKey
puts("defer #{model_id(model)}")
deferred.push(model)
ReduxApplicationRecord.transaction do
begin
import_model(model)
rescue ActiveRecord::InvalidForeignKey
$stderr.puts("defer #{model_id(model)}")
deferred.push(model)
end
end
end
deferred.each do |model|
import_model(model)
ReduxApplicationRecord.transaction do
deferred.each do |model|
import_model(model)
rescue
end
end
end
@@ -77,14 +79,14 @@ class DbSampler
exists = model.class.exists?(pk => id)
if exists
puts("skipped existing #{model_id(model)}")
$stderr.puts("skipped existing #{model_id(model)}")
else
model2 = model.class.new
model.attribute_names.map(&:to_sym).each do |attr|
model2.write_attribute(attr, model.read_attribute(attr))
end
model2.save(validate: false)
puts("imported #{model_id(model)}")
$stderr.puts("imported #{model_id(model)}")
end
end
@@ -94,18 +96,20 @@ class DbSampler
user_depth += 1 if is_user
return unless @handled.add?(model)
unless user_depth > 1 && is_user
assocs = SCHEMA[model.class] || raise("invalid: #{model.class.name}")
assocs.each do |assoc|
model2 = model.send(assoc)
next unless model2
if model2.respond_to? :each
model2.each do |model3|
handle_model(model3, level + 1, user_depth)
end
else
handle_model(model2, level + 1, user_depth)
assocs = SCHEMA[model.class] || raise("invalid: #{model.class.name}")
assocs.each do |assoc|
if user_depth > 1
next unless [:avatar, :disco].include?(assoc)
end
model2 = model.send(assoc)
next unless model2
if model2.respond_to? :each
model2.each do |model3|
handle_model(model3, level + 1, user_depth)
end
else
handle_model(model2, level + 1, user_depth)
end
end
@@ -113,9 +117,9 @@ class DbSampler
end
def dump(model, level)
@file.puts(Base64.strict_encode64(Marshal.dump(model)))
@file.puts(Base64.strict_encode64(Zstd.compress(Marshal.dump(model), 1)))
id = model.send(model.class.primary_key)
id = HexUtil.bin2hex(id) if model.class.primary_key == "sha256"
puts ("-" * level) + " dumped #{model.class.name}/#{id}"
$stderr.puts ("-" * level) + " dumped #{model.class.name}/#{id}"
end
end

View File

@@ -219,6 +219,10 @@ class Domain::Fa::User < ReduxApplicationRecord
# TODO - maybe can look for posts as well, those might list an avatar
end
def to_param
url_name
end
private
def similar_users_by(factor_col, exclude_followed_by)

View File

@@ -1,38 +1,16 @@
<% content_for :head do %>
<style type="text/css" data-turbolinks-track>
title {
border-collapse: collapse;
}
table td {
border-right: 1px solid black;
padding: 0 0.5em;
}
table td:last-child {
border-right: none;
}
td.right {
text-align: right;
}
</style>
<% end %>
<h1>FurAffinity Posts <%= page_str(params) %></h1>
<div id="domain_fa_posts">
<%= link_to "Previous page", path_to_prev_page(@posts) %>
<%= link_to "Next page", path_to_next_page(@posts) %>
<table>
<tr>
<th>Seen</th>
<th>Scanned</th>
<th>File</th>
<th>State</th>
<th>ID</th>
<th>Title</th>
<th>Creator</th>
</tr>
<% @posts.each do |post| %>
<%= render partial: "index_row", locals: { post: post } %>
<% end %>
</table>
<%= link_to "Previous page", path_to_prev_page(@posts) %>
<%= link_to "Next page", path_to_next_page(@posts) %>
<div class='mx-auto'>
<% if @user %>
<h1 class='text-2xl'><%= link_to(@user.name, @user, class: "underline") %>'s posts</h1>
<% else %>
<h1 class='text-2xl'>All FurAffinity posts, page <%= page_str(params) %></h1>
<% end %>
</div>
<% # link_to "Previous page", path_to_prev_page(@posts) %>
<% # link_to "Next page", path_to_next_page(@posts) %>
<div class='flex-row'>
<% @posts.each do |post| %>
<div class=''>
<img class='' alt='<%= post.title %>' src='<%= contents_log_entry_path(post.file, thumb: "small") %>' />
</div>
<% end %>
</div>

View File

@@ -1,14 +1,53 @@
<p style="color: green"><%= notice %></p>
<h1>Users</h1>
<div id="domain_fa_users">
<% @domain_fa_users.each do |domain_fa_user| %>
<%= render domain_fa_user %>
<p>
<%= link_to "Show this user", domain_fa_user %>
</p>
<section class='w-full'>
<h1>Users</h1>
<% @users.each do |user| %>
<div class='mx-auto max-w-xl bg-slate-200 border-slate-300 border-2 my-2 p-2 rounded flex flex-row'>
<% if (a = user.avatar&.log_entry) %>
<img class='rounded-sm' alt='<%= user.name %> avatar' src='<%= contents_log_entry_path(a) %>' />
<% else %>
<div>(No avatar)</div>
<% end %>
<div class='flex-grow ml-2 flex-col'>
<div class='flex-row'>
<a
class='
text-lg text-black font-semibold
underline decoration-dotted hover:decoration-solid'
href='<%= domain_fa_user_path(user) %>'><%= user.name %></a>
<a
class='
m-l-2 text-md text-slate-600
underline decoration-dotted hover:decoration-solid'
href="<%= domain_fa_user_posts_path(user) %>"
>
<%= pluralize(user.posts.count, "post") %>
</a>
</div>
<p class='block text-md text-slate-600'>
<% if user.scanned_page_at %>
Scanned page <%= time_ago_in_words(user.scanned_page_at) %> ago
<% else %>
Not yet scanned page
<% end %>
</p>
<p class='block text-md text-slate-600'>
<% if user.scanned_gallery_at %>
Scanned gallery <%= time_ago_in_words(user.scanned_gallery_at) %> ago
<% else %>
Not yet scanned gallery
<% end %>
</p>
</div>
<div class='flex'>
<a class='
self-start inline
border-b text-slate-600 border-slate-600 border-dashed
hover:border-black hover:text-black
' target='_blank' rel='noopener noreferrer' href='https://www.furaffinity.net/user/<%= user.url_name %>'>
FurAffinity
<img class='w-4 h-4 align-text-top inline' src='<%= image_path("arrow-top-right-on-square.svg") %>'>
</a>
</div>
</div>
<% end %>
</div>
<%= link_to "New user", new_domain_fa_user_path %>
</section>

View File

@@ -1,10 +1,7 @@
<p style="color: green"><%= notice %></p>
<%= render @domain_fa_user %>
<%= render @user %>
<div>
<%= link_to "Edit this user", edit_domain_fa_user_path(@domain_fa_user) %> |
<%= link_to "Edit this user", edit_domain_fa_user_path(@user) %> |
<%= link_to "Back to users", domain_fa_users_path %>
<%= button_to "Destroy this user", @domain_fa_user, method: :delete %>
<%= button_to "Destroy this user", @user, method: :delete %>
</div>

View File

@@ -30,7 +30,9 @@ Rails.application.routes.draw do
namespace :domain do
namespace :fa do
resources :users
resources :users, param: :url_name do
resources :posts, controller: "/domain/fa/posts"
end
resources :posts, param: :fa_id, only: [:index, :show] do
post :scan_post, on: :member
end
@@ -39,6 +41,8 @@ Rails.application.routes.draw do
# Defines the root path route ("/")
# root "articles#index"
resources :blobs, only: [:show], slug: :sha256
resources :log_entries, only: [:index, :show] do
get :contents, on: :member
get :stats, on: :collection

View File

@@ -1,6 +1,7 @@
const defaultTheme = require("tailwindcss/defaultTheme");
module.exports = {
mode: "jit",
content: [
"./public/*.html",
"./app/helpers/**/*.rb",