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 "Commentary", artist_commentary_versions_path(search: { post_id: @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