diff --git a/app/controllers/post_regenerations_controller.rb b/app/controllers/post_regenerations_controller.rb
new file mode 100644
index 000000000..f7466e0cc
--- /dev/null
+++ b/app/controllers/post_regenerations_controller.rb
@@ -0,0 +1,18 @@
+class PostRegenerationsController < ApplicationController
+ respond_to :html, :xml, :json, :js
+
+ def create
+ @post_regeneration = authorize PostRegeneration.new(creator: CurrentUser.user, **permitted_attributes(PostRegeneration))
+ @post_regeneration.execute_category_action!
+ @post_regeneration.save
+
+ respond_with(@post_regeneration, location: @post_regeneration.post)
+ end
+
+ def index
+ @post_regenerations = authorize PostRegeneration.paginated_search(params)
+ @post_regenerations = @post_regenerations.includes(:creator, :post) if request.format.html?
+
+ respond_with(@post_regenerations)
+ end
+end
diff --git a/app/logical/upload_service/utils.rb b/app/logical/upload_service/utils.rb
index 1ec214222..ec781d5dc 100644
--- a/app/logical/upload_service/utils.rb
+++ b/app/logical/upload_service/utils.rb
@@ -32,6 +32,22 @@ class UploadService
[preview_file, crop_file, sample_file]
end
+ def process_resizes(upload, file, original_post_id, media_file: nil)
+ media_file ||= upload.media_file
+ preview_file, crop_file, sample_file = Utils.generate_resizes(media_file)
+
+ begin
+ Utils.distribute_files(file, upload, :original, original_post_id: original_post_id) if file.present?
+ Utils.distribute_files(sample_file, upload, :large, original_post_id: original_post_id) if sample_file.present?
+ Utils.distribute_files(preview_file, upload, :preview, original_post_id: original_post_id) if preview_file.present?
+ Utils.distribute_files(crop_file, upload, :crop, original_post_id: original_post_id) if crop_file.present?
+ ensure
+ preview_file.try(:close!)
+ crop_file.try(:close!)
+ sample_file.try(:close!)
+ end
+ end
+
def process_file(upload, file, original_post_id: nil)
upload.file = file
media_file = upload.media_file
@@ -45,18 +61,7 @@ class UploadService
upload.validate!(:file)
upload.tag_string = "#{upload.tag_string} #{Utils.automatic_tags(media_file)}"
- preview_file, crop_file, sample_file = Utils.generate_resizes(media_file)
-
- begin
- Utils.distribute_files(file, upload, :original, original_post_id: original_post_id)
- Utils.distribute_files(sample_file, upload, :large, original_post_id: original_post_id) if sample_file.present?
- Utils.distribute_files(preview_file, upload, :preview, original_post_id: original_post_id) if preview_file.present?
- Utils.distribute_files(crop_file, upload, :crop, original_post_id: original_post_id) if crop_file.present?
- ensure
- preview_file.try(:close!)
- crop_file.try(:close!)
- sample_file.try(:close!)
- end
+ process_resizes(upload, file, original_post_id)
end
def automatic_tags(media_file)
diff --git a/app/models/post_regeneration.rb b/app/models/post_regeneration.rb
new file mode 100644
index 000000000..729f8740c
--- /dev/null
+++ b/app/models/post_regeneration.rb
@@ -0,0 +1,31 @@
+class PostRegeneration < ApplicationRecord
+ belongs_to :creator, :class_name => "User"
+ belongs_to :post
+
+ validates :category, inclusion: %w[iqdb resizes]
+
+ module SearchMethods
+ def search(params)
+ q = search_attributes(params, :id, :created_at, :updated_at, :category, :creator, :post)
+ q.apply_default_order(params)
+ end
+ end
+
+ extend SearchMethods
+
+ def execute_category_action!
+ if category == "iqdb"
+ post.update_iqdb_async
+ elsif category == "resizes"
+ media_file = MediaFile.open(post.file, frame_data: post.pixiv_ugoira_frame_data)
+ UploadService::Utils.process_resizes(post, nil, post.id, media_file: media_file)
+ else
+ # should never happen
+ raise Error, "Unknown category: #{category}"
+ end
+ end
+
+ def self.searchable_includes
+ [:creator, :post]
+ end
+end
diff --git a/app/policies/post_regeneration_policy.rb b/app/policies/post_regeneration_policy.rb
new file mode 100644
index 000000000..25cc57956
--- /dev/null
+++ b/app/policies/post_regeneration_policy.rb
@@ -0,0 +1,9 @@
+class PostRegenerationPolicy < ApplicationPolicy
+ def create?
+ user.is_moderator?
+ end
+
+ def permitted_attributes_for_create
+ [:post_id, :category]
+ end
+end
diff --git a/app/views/post_regenerations/create.js.erb b/app/views/post_regenerations/create.js.erb
new file mode 100644
index 000000000..d45a54f1e
--- /dev/null
+++ b/app/views/post_regenerations/create.js.erb
@@ -0,0 +1,5 @@
+<% if @post_regeneration.errors.any? %>
+ Danbooru.error("<%= j @post_regeneration.errors.full_messages.join(',') %>");
+<% else %>
+ Danbooru.notice("Post regenerated");
+<% end %>
diff --git a/app/views/post_regenerations/index.html.erb b/app/views/post_regenerations/index.html.erb
new file mode 100644
index 000000000..cea7ac3a1
--- /dev/null
+++ b/app/views/post_regenerations/index.html.erb
@@ -0,0 +1,17 @@
+
+
+
Post regenerations
+ <%= table_for @post_regenerations, width: "100%" do |t| %>
+ <% t.column "Post", width: "1%" do |regeneration| %>
+ <%= PostPresenter.preview(regeneration.post, show_deleted: true) %>
+ <% end %>
+ <% t.column :category %>
+ <% t.column "Creator", width: "10%" do |regeneration| %>
+ <%= compact_time regeneration.created_at %>
+
by <%= link_to_user regeneration.creator %>
+ <% end %>
+ <% end %>
+
+ <%= numbered_paginator(@post_regenerations) %>
+
+
diff --git a/app/views/posts/partials/show/_options.html.erb b/app/views/posts/partials/show/_options.html.erb
index 7190bdf4c..2b336a854 100644
--- a/app/views/posts/partials/show/_options.html.erb
+++ b/app/views/posts/partials/show/_options.html.erb
@@ -86,4 +86,9 @@
<%= link_to "Replace image", new_post_replacement_path(post_id: post.id), remote: true %>
<% end %>
<% end %>
+
+ <% if policy(PostRegeneration).create? %>
+ <%= link_to "Regenerate IQDB", post_regenerations_path(post_regeneration: {post_id: post.id, category: "iqdb"}), remote: true, method: :post %>
+ <%= link_to "Regenerate image sizes", post_regenerations_path(post_regeneration: {post_id: post.id, category: "resizes"}), remote: true, method: :post %>
+ <% end %>
diff --git a/app/views/posts/show.html.erb b/app/views/posts/show.html.erb
index a2f202f27..6f5152225 100644
--- a/app/views/posts/show.html.erb
+++ b/app/views/posts/show.html.erb
@@ -33,6 +33,7 @@
<%= link_to "Moderation", post_events_path(@post.id) %>
<%= link_to "Replacements", post_replacements_path(search: {post_id: @post.id }) %>
+ <%= link_to "Regenerations", post_regenerations_path(search: {post_id: @post.id }) %>
<% end %>
diff --git a/config/routes.rb b/config/routes.rb
index 13e8a809c..5fa7b3238 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -178,6 +178,7 @@ Rails.application.routes.draw do
get :search
end
end
+ resources :post_regenerations, :only => [:index, :create]
resources :post_replacements, :only => [:index, :new, :create, :update]
resources :post_votes, only: [:index]
diff --git a/db/migrate/20201121180345_create_post_regenerations.rb b/db/migrate/20201121180345_create_post_regenerations.rb
new file mode 100644
index 000000000..ec88f7bbe
--- /dev/null
+++ b/db/migrate/20201121180345_create_post_regenerations.rb
@@ -0,0 +1,13 @@
+class CreatePostRegenerations < ActiveRecord::Migration[6.0]
+ def change
+ create_table :post_regenerations do |t|
+ t.timestamps
+ t.integer :creator_id, null: false
+ t.integer :post_id, null: false
+ t.string :category, null: false
+
+ t.index :creator_id
+ t.index :post_id
+ end
+ end
+end
diff --git a/db/structure.sql b/db/structure.sql
index f57281afa..3afb26718 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -2724,6 +2724,39 @@ CREATE SEQUENCE public.post_flags_id_seq
ALTER SEQUENCE public.post_flags_id_seq OWNED BY public.post_flags.id;
+--
+-- Name: post_regenerations; Type: TABLE; Schema: public; Owner: -
+--
+
+CREATE TABLE public.post_regenerations (
+ id bigint NOT NULL,
+ created_at timestamp(6) without time zone NOT NULL,
+ updated_at timestamp(6) without time zone NOT NULL,
+ creator_id integer NOT NULL,
+ post_id integer NOT NULL,
+ category character varying NOT NULL
+);
+
+
+--
+-- Name: post_regenerations_id_seq; Type: SEQUENCE; Schema: public; Owner: -
+--
+
+CREATE SEQUENCE public.post_regenerations_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+
+--
+-- Name: post_regenerations_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
+--
+
+ALTER SEQUENCE public.post_regenerations_id_seq OWNED BY public.post_regenerations.id;
+
+
--
-- Name: post_replacements; Type: TABLE; Schema: public; Owner: -
--
@@ -4137,6 +4170,13 @@ ALTER TABLE ONLY public.post_disapprovals ALTER COLUMN id SET DEFAULT nextval('p
ALTER TABLE ONLY public.post_flags ALTER COLUMN id SET DEFAULT nextval('public.post_flags_id_seq'::regclass);
+--
+-- Name: post_regenerations id; Type: DEFAULT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.post_regenerations ALTER COLUMN id SET DEFAULT nextval('public.post_regenerations_id_seq'::regclass);
+
+
--
-- Name: post_replacements id; Type: DEFAULT; Schema: public; Owner: -
--
@@ -4499,6 +4539,14 @@ ALTER TABLE ONLY public.post_flags
ADD CONSTRAINT post_flags_pkey PRIMARY KEY (id);
+--
+-- Name: post_regenerations post_regenerations_pkey; Type: CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.post_regenerations
+ ADD CONSTRAINT post_regenerations_pkey PRIMARY KEY (id);
+
+
--
-- Name: post_replacements post_replacements_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
@@ -6752,6 +6800,20 @@ CREATE INDEX index_post_flags_on_reason_tsvector ON public.post_flags USING gin
CREATE INDEX index_post_flags_on_status ON public.post_flags USING btree (status);
+--
+-- Name: index_post_regenerations_on_creator_id; Type: INDEX; Schema: public; Owner: -
+--
+
+CREATE INDEX index_post_regenerations_on_creator_id ON public.post_regenerations USING btree (creator_id);
+
+
+--
+-- Name: index_post_regenerations_on_post_id; Type: INDEX; Schema: public; Owner: -
+--
+
+CREATE INDEX index_post_regenerations_on_post_id ON public.post_regenerations USING btree (post_id);
+
+
--
-- Name: index_post_replacements_on_creator_id; Type: INDEX; Schema: public; Owner: -
--
@@ -7520,6 +7582,7 @@ INSERT INTO "schema_migrations" (version) VALUES
('20200520060951'),
('20200803022359'),
('20200816175151'),
+('20201121180345'),
('20201201211748'),
('20201213052805'),
('20201219201007'),
diff --git a/test/factories/post_regeneration.rb b/test/factories/post_regeneration.rb
new file mode 100644
index 000000000..00087ac02
--- /dev/null
+++ b/test/factories/post_regeneration.rb
@@ -0,0 +1,5 @@
+FactoryBot.define do
+ factory(:post_regeneration) do
+ post factory: :post, source: FFaker::Internet.http_url
+ end
+end
diff --git a/test/functional/post_regenerations_controller_test.rb b/test/functional/post_regenerations_controller_test.rb
new file mode 100644
index 000000000..119d5741a
--- /dev/null
+++ b/test/functional/post_regenerations_controller_test.rb
@@ -0,0 +1,49 @@
+require 'test_helper'
+
+class PostRegenerationsControllerTest < ActionDispatch::IntegrationTest
+ context "The post regenerations controller" do
+ setup do
+ @mod = create(:moderator_user, name: "yukari", created_at: 1.month.ago)
+ as(@mod) do
+ @post = create(:post, source: "https://google.com", tag_string: "touhou")
+ @post_regeneration = create(:post_regeneration, creator: @mod, category: "iqdb")
+ end
+ end
+
+ context "create action" do
+ should "render" do
+ assert_difference("PostRegeneration.count") do
+ post_auth post_regenerations_path, @mod, params: {format: :json, post_regeneration: {post_id: @post.id, category: "iqdb"}}
+ assert_response :success
+ end
+ end
+
+ should "not allow non-mods to regenerate posts" do
+ assert_difference("PostRegeneration.count", 0) do
+ post_auth post_regenerations_path, create(:user), params: {format: :json, post_regeneration: {post_id: @post.id, category: "iqdb"}}
+ assert_response 403
+ end
+ end
+ end
+
+ context "index action" do
+ setup do
+ @admin = create(:admin_user)
+ as(@admin) { @admin_regeneration = create(:post_regeneration, post: @post, creator: @admin, category: "resizes") }
+ end
+
+ should "render" do
+ get post_regenerations_path
+ assert_response :success
+ end
+
+ should respond_to_search({}).with { [@admin_regeneration, @post_regeneration] }
+ should respond_to_search(category: "iqdb").with { @post_regeneration }
+
+ context "using includes" do
+ should respond_to_search(post_tags_match: "touhou").with { @admin_regeneration }
+ should respond_to_search(creator: {level: User::Levels::ADMIN}).with { @admin_regeneration }
+ end
+ end
+ end
+end