module BrandingAttachments extend ActiveSupport::Concern private def attach_branding_logo(record) file = params.dig(:branding, :logo_file) record.logo_file.attach(file) if file.present? end def attach_cover_image(record, entitlements:) remove_flag = params[:remove_cover].to_s == "1" || params.dig(:match, :remove_cover).to_s == "1" if remove_flag record.cover_image.purge if record.cover_image.attached? record.cover_slate.purge if record.cover_slate.attached? return end file = params.dig(:branding, :cover_image) || params[:cover_image] return if file.blank? entitlements.assert_can_upload_cover! validate_cover_file!(file) record.cover_slate.purge if record.cover_slate.attached? record.cover_image.attach(file) end def validate_cover_file!(file) allowed_types = Coverable::COVER_IMAGE_TYPES # Rileva MIME dal contenuto (magic bytes) ignorando il declared_type del client. io = file.respond_to?(:tempfile) ? file.tempfile : file io.rewind if io.respond_to?(:rewind) detected = Marcel::MimeType.for(io, name: file.respond_to?(:original_filename) ? file.original_filename : nil) io.rewind if io.respond_to?(:rewind) unless detected.in?(allowed_types) raise CoverUploadError, I18n.t("coverable.errors.invalid_type") end byte_size = file.respond_to?(:size) ? file.size : File.size(file.path) if byte_size > Coverable::MAX_COVER_BYTES raise CoverUploadError, I18n.t("coverable.errors.too_large") end end class CoverUploadError < StandardError; end def enqueue_cover_slate_generation(record) GenerateCoverSlateJob.enqueue_for(record) if record.cover_image.attached? end def normalize_hex_color(value, fallback) v = value.to_s.strip v = "##{v}" if v.match?(/\A[0-9A-Fa-f]{6}\z/) v.match?(Brandable::HEX_COLOR) ? v : fallback end end