class PostImportsController < ApplicationController before_action :require_member! def parse source = if params[:format] == 'google_sheets' PostImportGoogleSheetsFetcher.fetch!(params[:source], rate_key: current_user.id) else params[:source] end format = params[:format] == 'google_sheets' ? 'csv' : params[:format] parsed = PostImportSourceParser.new(source:, format:, has_header: params[:has_header], json_path: params[:json_path]).parse render json: parsed rescue ArgumentError => e render_bad_request(e.message) end def preview parsed = parsed_params url_column = params[:url_column].presence || (parsed[:columns].length == 1 ? 0 : nil) return render_bad_request('URL 列を選択してください.') if url_column.nil? rows = PostImportPreviewer.new(parsed:, url_column:, mappings: params[:mappings], existing: params[:existing]).preview render json: parsed.slice(:columns).merge(rows:) rescue ArgumentError => e render_bad_request(e.message) end def validate rows = validate_rows_params changed_row = Integer(params[:changed_row], exception: false) result = PostImportPreviewer.new(parsed: { columns: [], rows: [] }, url_column: 0, mappings: {}, existing: params[:existing]).preview_rows( rows:, fetch_metadata: changed_row, metadata_cache: { }) render json: { rows: result } rescue ArgumentError => e render_bad_request(e.message) end def create result = PostImportRunner.new(actor: current_user, rows: params[:rows].to_a).run render json: result, status: result[:created].positive? ? :created : :ok rescue ArgumentError => e render_bad_request(e.message) end private def require_member! return head :unauthorized unless current_user return if current_user.gte_member? head :forbidden end def parsed_params value = params.require(:parsed).to_unsafe_h.deep_transform_keys { _1.to_s.underscore } columns = Array(value['columns']) rows = Array(value['rows']) raise ArgumentError, '解析結果の形式が不正です.' unless columns.all?(Hash) && rows.all?(Hash) raise ArgumentError, '取込件数が多すぎます.' if rows.length > PostImportSourceParser::MAX_ROWS raise ArgumentError, '列数が多すぎます.' if columns.length > 50 unless rows.all? { |row| Array(row['values']).length <= columns.length && Array(row['values']).all? { _1.is_a?(String) && _1.bytesize <= PostImportSourceParser::MAX_CELL_BYTES } } raise ArgumentError, '解析結果のセルが不正です.' end raise ArgumentError, '解析結果が大きすぎます.' if rows.sum { Array(_1['values']).sum(&:bytesize) } > PostImportSourceParser::MAX_BYTES { columns: columns.map { |column| column.slice('key', 'label') }, rows: rows.map { |row| { source_row: row['source_row'], values: Array(row['values']) } } } end def validate_rows_params rows = Array(params[:rows]) raise ArgumentError, '取込件数が多すぎます.' if rows.length > PostImportSourceParser::MAX_ROWS rows.map do |row| value = ActionController::Parameters.new(row).permit(:source_row, :sourceRow, :url, attributes: {}, provenance: {}, fetch_warnings: [], validation_warnings: []) normalised = value.to_h.deep_transform_keys { _1.to_s.underscore } attributes = normalised.fetch('attributes', { }) provenance = normalised.fetch('provenance', { }) allowed = PostImportPreviewer::FIELDS unless attributes.is_a?(Hash) && provenance.is_a?(Hash) && (attributes.keys - allowed).empty? && (provenance.keys - (allowed + ['url'])).empty? raise ArgumentError, '行の形式が不正です.' end raise ArgumentError, 'セルが大きすぎます.' if [normalised['url'], *attributes.values].any? { _1.to_s.bytesize > PostImportSourceParser::MAX_CELL_BYTES } normalised.symbolize_keys end end end