From 460635e940fca637bf41499cb8d4070beb9a3db7 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 4 Mar 2022 19:03:02 -0300 Subject: [PATCH 01/43] ya no hace falta analizar archivos en primer plano --- config/initializers/analyze_job.rb | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 config/initializers/analyze_job.rb diff --git a/config/initializers/analyze_job.rb b/config/initializers/analyze_job.rb deleted file mode 100644 index f268e0dd..00000000 --- a/config/initializers/analyze_job.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -# TODO: Estamos procesando el análisis de los archivos en el momento -# porque queremos obtener la ruta del archivo en el momento y no -# después. Necesitaríamos poder generar el vínculo en el -# repositorio a destiempo, modificando el Job de ActiveStorage -ActiveStorage::AnalyzeJob.queue_adapter = :inline From 29950e4380b4c6d5122a0bfd3f8024178b44a40d Mon Sep 17 00:00:00 2001 From: f Date: Fri, 4 Mar 2022 19:05:11 -0300 Subject: [PATCH 02/43] cargar decorators --- config/application.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/config/application.rb b/config/application.rb index 7326ae0f..0e6e7f44 100644 --- a/config/application.rb +++ b/config/application.rb @@ -38,6 +38,12 @@ module Sutty config.active_storage.variant_processor = :vips + config.to_prepare do + Dir.glob(File.join(File.dirname(__FILE__), '..', 'app', '**', '*_decorator.rb')) do |c| + Rails.configuration.cache_classes ? require(c) : load(c) + end + end + config.after_initialize do ActiveStorage::DirectUploadsController.include ActiveStorage::AuthenticatedDirectUploadsController From 3de1228c26541185a62fa7a528dfc170308018bf Mon Sep 17 00:00:00 2001 From: f Date: Fri, 4 Mar 2022 19:08:09 -0300 Subject: [PATCH 03/43] implementa un servicio de carga de archivos en jekyll --- .../active_storage/service/jekyll_service.rb | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 app/lib/active_storage/service/jekyll_service.rb diff --git a/app/lib/active_storage/service/jekyll_service.rb b/app/lib/active_storage/service/jekyll_service.rb new file mode 100644 index 00000000..686c3979 --- /dev/null +++ b/app/lib/active_storage/service/jekyll_service.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module ActiveStorage + module Service + # Sube los archivos a cada repositorio y los agrega al LFS de su + # repositorio git. + # + # @todo: Implementar LFS. No nos gusta mucho la idea porque duplica + # el espacio en disco, pero es la única forma que tenemos (hasta que + # implementemos IPFS) para poder transferir los archivos junto con el + # sitio. + class JekyllService < Service::DiskService + BLOB_NAME = 'blob' + + # Para poder guardar el archivo con el nombre original pero poder + # recuperarlo durante el download, luego de subirlo le cambiamos el + # nombre y creamos un link simbólico a un nombre conocido. + def upload(key, io, checksum: nil, **options) + super.tap do + path = path_for(key) + filename = options[:filename].to_s + + FileUtils.mv path, path.sub(/#{BLOB_NAME}\z/, filename) + FileUtils.ln_s filename, path + end + end + + # Mantener retrocompatibilidad con cómo gestionamos los archivos + # subidos hasta ahora. + # + # @param :key [String] + # @return [String] + def folder_for(key) + key + end + + # Crea una ruta para la llave con un nombre conocido. + def path_for(key) + File.join root, folder_for(key), BLOB_NAME + end + end + end +end From 873f2c7bcb8db83a1bdc46580158ff28ce3e67e9 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 4 Mar 2022 19:09:06 -0300 Subject: [PATCH 04/43] informar cuando no se pudo cargar el archivo --- app/models/metadata_file.rb | 1 + config/locales/en.yml | 2 ++ config/locales/es.yml | 2 ++ 3 files changed, 5 insertions(+) diff --git a/app/models/metadata_file.rb b/app/models/metadata_file.rb index 80cefa27..b020d078 100644 --- a/app/models/metadata_file.rb +++ b/app/models/metadata_file.rb @@ -19,6 +19,7 @@ class MetadataFile < MetadataTemplate errors << I18n.t("metadata.#{type}.site_invalid") if site.invalid? errors << I18n.t("metadata.#{type}.path_required") if path_missing? errors << I18n.t("metadata.#{type}.no_file_for_description") if no_file_for_description? + errors << I18n.t("metadata.#{type}.attachment_missing") unless static_file errors.compact! errors.empty? diff --git a/config/locales/en.yml b/config/locales/en.yml index b814796d..8bbe3621 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -41,10 +41,12 @@ en: not_an_image: 'Not an image' path_required: 'Missing image for upload' no_file_for_description: "Description with no associated image" + attachment_missing: "I couldn't save the image :(" file: site_invalid: 'The file cannot be stored if the site configuration is not valid' path_required: "Missing file for upload" no_file_for_description: "Description with no associated file" + attachment_missing: "I couldn't save the file :(" event: zone_missing: 'Inexistent timezone' date_missing: 'Event date is required' diff --git a/config/locales/es.yml b/config/locales/es.yml index a6fbd407..93dd162e 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -41,10 +41,12 @@ es: not_an_image: 'No es una imagen' path_required: 'Se necesita una imagen' no_file_for_description: 'Se envió una descripción sin imagen asociada' + attachment_missing: 'no pude guardar el archivo :(' file: site_invalid: 'El archivo no se puede almacenar si la configuración del sitio no es válida' path_required: 'Se necesita un archivo' no_file_for_description: 'se envió una descripción sin archivo asociado' + attachment_missing: 'no pude guardar el archivo :(' event: zone_missing: 'El huso horario no es correcto' date_missing: 'La fecha es obligatoria' From c4139c4b92746c84cdbdaaffe1fea31a69afc9a8 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 4 Mar 2022 19:12:06 -0300 Subject: [PATCH 05/43] =?UTF-8?q?eliminar=20c=C3=B3digo=20que=20no=20se=20?= =?UTF-8?q?usa?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/metadata_file.rb | 6 ------ 1 file changed, 6 deletions(-) diff --git a/app/models/metadata_file.rb b/app/models/metadata_file.rb index b020d078..58adc857 100644 --- a/app/models/metadata_file.rb +++ b/app/models/metadata_file.rb @@ -35,12 +35,6 @@ class MetadataFile < MetadataTemplate value['path'].is_a?(String) end - # Determina si la ruta es opcional pero deja pasar si la ruta se - # especifica - def path_optional? - !required && !path? - end - # Asociar la imagen subida al sitio y obtener la ruta # # XXX: Si evitamos guardar cambios con changed? no tenemos forma de From 3d5267451eec0e879fad28ef4086cd240884d683 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 4 Mar 2022 19:15:38 -0300 Subject: [PATCH 06/43] =?UTF-8?q?eliminar=20c=C3=B3digo=20que=20ya=20no=20?= =?UTF-8?q?se=20usa?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/metadata_file.rb | 65 ------------------------------------- 1 file changed, 65 deletions(-) diff --git a/app/models/metadata_file.rb b/app/models/metadata_file.rb index 58adc857..86644fcc 100644 --- a/app/models/metadata_file.rb +++ b/app/models/metadata_file.rb @@ -93,71 +93,6 @@ class MetadataFile < MetadataTemplate private - def filemagic - @filemagic ||= FileMagic.new(FileMagic::MAGIC_MIME) - end - - # @return [Pathname] - def path - @path ||= Pathname.new(File.join(site.path, value['path'])) - end - - def file - return unless path? - - @file ||= - case value['path'] - when ActionDispatch::Http::UploadedFile then value['path'].tempfile.path - when String then File.join(site.path, value['path']) - end - end - - # Hacemos un link duro para colocar el archivo dentro del repositorio - # y no duplicar el espacio que ocupan. Esto requiere que ambos - # directorios estén dentro del mismo punto de montaje. - # - # XXX: Asumimos que el archivo destino no existe porque siempre - # contiene una key única. - # - # @return [Boolean] - def hardlink - return if hardlink? - return if File.exist? destination_path - - FileUtils.mkdir_p(File.dirname(destination_path)) - FileUtils.ln(uploaded_path, destination_path).zero? - end - - def hardlink? - File.stat(uploaded_path).ino == File.stat(destination_path).ino - rescue Errno::ENOENT - false - end - - # Obtener la ruta al archivo - # https://stackoverflow.com/a/53908358 - def uploaded_relative_path - ActiveStorage::Blob.service.path_for(static_file.key) - end - - # @return [String] - def uploaded_path - Rails.root.join uploaded_relative_path - end - - # La ruta del archivo mantiene el nombre original pero contiene el - # nombre interno y único del archivo para poder relacionarlo con el - # archivo subido en Sutty. - # - # @return [String] - def relative_destination_path - @relative_destination_path ||= File.join('public', static_file.key, static_file.filename.to_s) - end - - # @return [String] - def destination_path - @destination_path ||= File.join(site.path, relative_destination_path) - end # No hay archivo pero se lo describió def no_file_for_description? From d8fd25a7cb32ab6cdec028f4d054cf398df4b97c Mon Sep 17 00:00:00 2001 From: f Date: Fri, 4 Mar 2022 19:16:36 -0300 Subject: [PATCH 07/43] =?UTF-8?q?chequear=20la=20descripci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/metadata_file.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/models/metadata_file.rb b/app/models/metadata_file.rb index 86644fcc..20d093b2 100644 --- a/app/models/metadata_file.rb +++ b/app/models/metadata_file.rb @@ -91,11 +91,14 @@ class MetadataFile < MetadataTemplate value['path'].present? end - private + def description? + value['description'].present? + end + private # No hay archivo pero se lo describió def no_file_for_description? - value['description'].present? && value['path'].blank? + !path? && description? end end From 63f8b869eb185dd9c607ed4b684baf9ab478a08d Mon Sep 17 00:00:00 2001 From: f Date: Fri, 4 Mar 2022 19:17:03 -0300 Subject: [PATCH 08/43] fixup! cargar decorators --- config/application.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/application.rb b/config/application.rb index 0e6e7f44..031c1909 100644 --- a/config/application.rb +++ b/config/application.rb @@ -39,7 +39,7 @@ module Sutty config.active_storage.variant_processor = :vips config.to_prepare do - Dir.glob(File.join(File.dirname(__FILE__), '..', 'app', '**', '*_decorator.rb')) do |c| + Dir.glob(File.join(File.dirname(__FILE__), '..', 'app', '**', '*_decorator.rb')).sort.each do |c| Rails.configuration.cache_classes ? require(c) : load(c) end end From 37deb361785d9f79c2d721898c3545a5c7323985 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 4 Mar 2022 19:19:40 -0300 Subject: [PATCH 09/43] conseguir la ruta al archivo --- app/models/metadata_file.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/models/metadata_file.rb b/app/models/metadata_file.rb index 20d093b2..89e6d461 100644 --- a/app/models/metadata_file.rb +++ b/app/models/metadata_file.rb @@ -83,6 +83,15 @@ class MetadataFile < MetadataTemplate end end + # Obtiene la ruta absoluta al archivo + # + # @return [Pathname] + def pathname + raise NoMethodError unless uploaded? + + @pathname ||= Pathname.new(File.join(site.path, value['path'])) + end + def key_from_path path.dirname.basename.to_s end From 03d9913f58f4be0bd0e2c3417577bab6f8a67c4a Mon Sep 17 00:00:00 2001 From: f Date: Fri, 4 Mar 2022 19:21:03 -0300 Subject: [PATCH 10/43] obtener el archivo subido --- app/models/metadata_file.rb | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/app/models/metadata_file.rb b/app/models/metadata_file.rb index 89e6d461..e4c0d037 100644 --- a/app/models/metadata_file.rb +++ b/app/models/metadata_file.rb @@ -66,18 +66,19 @@ class MetadataFile < MetadataTemplate # XXX: La última opción provoca archivos duplicados, pero es lo mejor # que tenemos hasta que resolvamos https://0xacab.org/sutty/sutty/-/issues/213 # - # @return [ActiveStorage::Attachment] + # @todo encontrar una forma de obtener el attachment sin tener que + # recurrir al último subido. + # + # @return [ActiveStorage::Attachment,nil] def static_file - return unless path? - @static_file ||= case value['path'] when ActionDispatch::Http::UploadedFile site.static_files.last if site.static_files.attach(value['path']) when String - if (blob = ActiveStorage::Blob.where(key: key_from_path).pluck(:id).first) - site.static_files.find_by(blob_id: blob) - elsif site.static_files.attach(io: path.open, filename: path.basename) + if (blob_id = ActiveStorage::Blob.where(key: key_from_path).pluck(:id).first) + site.static_files.find_by(blob_id: blob_id) + elsif path? && pathname.exist? && site.static_files.attach(io: pathname.open, filename: pathname.basename) site.static_files.last end end @@ -92,8 +93,11 @@ class MetadataFile < MetadataTemplate @pathname ||= Pathname.new(File.join(site.path, value['path'])) end + # Obtiene la key del attachment a partir de la ruta + # + # @return [String] def key_from_path - path.dirname.basename.to_s + pathname.dirname.basename.to_s end def path? From fdbe724f7c92953e9ba2cfa74f8470a6fa617c2e Mon Sep 17 00:00:00 2001 From: f Date: Fri, 4 Mar 2022 19:21:26 -0300 Subject: [PATCH 11/43] asociar el archivo subido al post --- app/models/metadata_file.rb | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/app/models/metadata_file.rb b/app/models/metadata_file.rb index e4c0d037..1c859481 100644 --- a/app/models/metadata_file.rb +++ b/app/models/metadata_file.rb @@ -43,13 +43,7 @@ class MetadataFile < MetadataTemplate # repetida. def save value['description'] = sanitize value['description'] - - if path? - hardlink - value['path'] = relative_destination_path - else - value['path'] = nil - end + value['path'] = static_file ? relative_destination_path_with_filename.to_s : nil true end @@ -110,6 +104,28 @@ class MetadataFile < MetadataTemplate private + # Obtener la ruta al archivo relativa al sitio + # + # @return [Pathname] + def destination_path + Pathname.new(static_file_path) + end + + # Agrega el nombre de archivo a la ruta para tener retrocompatibilidad + # + # @return [Pathname] + def destination_path_with_filename + destination_path.realpath + end + + def relative_destination_path_with_filename + destination_path_with_filename.relative_path_from(site.path) + end + + def static_file_path + static_file.blob.service.path_for(static_file.key) + end + # No hay archivo pero se lo describió def no_file_for_description? !path? && description? From f3df5504944cbdbf2f07aa0cd33f8fd8cd7f55a0 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 4 Mar 2022 19:21:48 -0300 Subject: [PATCH 12/43] =?UTF-8?q?no=20permitir=20subir=20im=C3=A1genes=20q?= =?UTF-8?q?ue=20no=20son=20para=20web?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/metadata_image.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/models/metadata_image.rb b/app/models/metadata_image.rb index f91a6273..f86c5c26 100644 --- a/app/models/metadata_image.rb +++ b/app/models/metadata_image.rb @@ -13,8 +13,6 @@ class MetadataImage < MetadataFile # Determina si es una imagen def image? - return true unless file - - filemagic.file(file).starts_with? 'image/' + static_file&.blob&.send(:web_image?) end end From 02b52b23b91b0c88a9c48b01604eb9ee22692d9f Mon Sep 17 00:00:00 2001 From: f Date: Fri, 4 Mar 2022 19:24:04 -0300 Subject: [PATCH 13/43] compartir el nombre de archivo con JekyllService --- app/models/active_storage/blob_decorator.rb | 26 +++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 app/models/active_storage/blob_decorator.rb diff --git a/app/models/active_storage/blob_decorator.rb b/app/models/active_storage/blob_decorator.rb new file mode 100644 index 00000000..9c01251a --- /dev/null +++ b/app/models/active_storage/blob_decorator.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module ActiveStorage + # Modificaciones a ActiveStorage::Blob + module BlobDecorator + extend ActiveSupport::Concern + + included do + # Permitir que llegue el nombre de archivo al servicio de subida de + # archivos. + # + # @return [Hash] + def service_metadata + if forcibly_serve_as_binary? + { content_type: ActiveStorage.binary_content_type, disposition: :attachment, filename: filename } + elsif !allowed_inline? + { content_type: content_type, disposition: :attachment, filename: filename } + else + { content_type: content_type, filename: filename } + end + end + end + end +end + +ActiveStorage::Blob.include ActiveStorage::BlobDecorator From 69e7df4c31b8f3b72d9e2cde2b7430bb3cef4f66 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 4 Mar 2022 19:26:56 -0300 Subject: [PATCH 14/43] instanciar JekyllService para cada sitio --- .../service/registry_decorator.rb | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 app/lib/active_storage/service/registry_decorator.rb diff --git a/app/lib/active_storage/service/registry_decorator.rb b/app/lib/active_storage/service/registry_decorator.rb new file mode 100644 index 00000000..f7f20784 --- /dev/null +++ b/app/lib/active_storage/service/registry_decorator.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module ActiveStorage + module Service + # Modificaciones a ActiveStorage::Service::Registry + module RegistryDecorator + extend ActiveSupport::Concern + + included do + # El mismo comportamiento que #fetch con el agregado de generar + # un {JekyllService} para cada sitio. + def fetch(name) + services.fetch(name.to_sym) do |key| + if configurations.include?(key) + services[key] = configurator.build(key) + elsif (site = Site.find_by_name(key)) + root = File.join(site.path, 'public') + services[key] = ActiveStorage::Service::JekyllService.new(root: root, public: true).tap do |s| + s.name = key.to_sym + end + elsif block_given? + yield key + else + raise KeyError, "Missing configuration for the #{key} Active Storage service. " \ + "Configurations available for the #{configurations.keys.to_sentence} services." + end + end + end + end + end + end +end + +ActiveStorage::Service::Registry.include ActiveStorage::Service::RegistryDecorator From 15e978c877cacc8b5eb3a6c79c5e0e9cc3349eda Mon Sep 17 00:00:00 2001 From: f Date: Sat, 5 Mar 2022 20:08:55 -0300 Subject: [PATCH 15/43] eran clases --- app/lib/active_storage/service/jekyll_service.rb | 2 +- app/lib/active_storage/service/registry_decorator.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/lib/active_storage/service/jekyll_service.rb b/app/lib/active_storage/service/jekyll_service.rb index 686c3979..173f6898 100644 --- a/app/lib/active_storage/service/jekyll_service.rb +++ b/app/lib/active_storage/service/jekyll_service.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module ActiveStorage - module Service + class Service # Sube los archivos a cada repositorio y los agrega al LFS de su # repositorio git. # diff --git a/app/lib/active_storage/service/registry_decorator.rb b/app/lib/active_storage/service/registry_decorator.rb index f7f20784..f6794607 100644 --- a/app/lib/active_storage/service/registry_decorator.rb +++ b/app/lib/active_storage/service/registry_decorator.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module ActiveStorage - module Service + class Service # Modificaciones a ActiveStorage::Service::Registry module RegistryDecorator extend ActiveSupport::Concern From 7f6063475bb63eed2cab8523d5f15d1ef71b15ea Mon Sep 17 00:00:00 2001 From: f Date: Sat, 5 Mar 2022 20:09:09 -0300 Subject: [PATCH 16/43] =?UTF-8?q?m=C3=A9todo=20gen=C3=A9rico=20para=20inst?= =?UTF-8?q?anciar=20el=20servicio?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/lib/active_storage/service/jekyll_service.rb | 7 +++++++ app/lib/active_storage/service/registry_decorator.rb | 5 +---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/lib/active_storage/service/jekyll_service.rb b/app/lib/active_storage/service/jekyll_service.rb index 173f6898..20dda523 100644 --- a/app/lib/active_storage/service/jekyll_service.rb +++ b/app/lib/active_storage/service/jekyll_service.rb @@ -12,6 +12,13 @@ module ActiveStorage class JekyllService < Service::DiskService BLOB_NAME = 'blob' + # Genera un servicio para un sitio determinado + def self.build_for_site(site:) + new(root: File.join(site.path, 'public'), public: true).tap do |js| + js.name = site.name.to_sym + end + end + # Para poder guardar el archivo con el nombre original pero poder # recuperarlo durante el download, luego de subirlo le cambiamos el # nombre y creamos un link simbólico a un nombre conocido. diff --git a/app/lib/active_storage/service/registry_decorator.rb b/app/lib/active_storage/service/registry_decorator.rb index f6794607..c7096356 100644 --- a/app/lib/active_storage/service/registry_decorator.rb +++ b/app/lib/active_storage/service/registry_decorator.rb @@ -14,10 +14,7 @@ module ActiveStorage if configurations.include?(key) services[key] = configurator.build(key) elsif (site = Site.find_by_name(key)) - root = File.join(site.path, 'public') - services[key] = ActiveStorage::Service::JekyllService.new(root: root, public: true).tap do |s| - s.name = key.to_sym - end + services[key] = ActiveStorage::Service::JekyllService.build_for_site(site: site) elsif block_given? yield key else From 10eef47ce8bfb0f233083e070207f7d1c8bbf578 Mon Sep 17 00:00:00 2001 From: f Date: Sat, 5 Mar 2022 20:09:37 -0300 Subject: [PATCH 17/43] todos los archivos subidos se asocian al sitio --- .../attached/changes/create_one_decorator.rb | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 app/lib/active_storage/attached/changes/create_one_decorator.rb diff --git a/app/lib/active_storage/attached/changes/create_one_decorator.rb b/app/lib/active_storage/attached/changes/create_one_decorator.rb new file mode 100644 index 00000000..bfb92478 --- /dev/null +++ b/app/lib/active_storage/attached/changes/create_one_decorator.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module ActiveStorage + module Attached::Changes::CreateOneDecorator + extend ActiveSupport::Concern + + included do + private + + # A partir de ahora todos los archivos se suben al servicio de + # cada sitio. + def attachment_service_name + record.name.to_sym + end + end + end +end + +ActiveStorage::Attached::Changes::CreateOne.include ActiveStorage::Attached::Changes::CreateOneDecorator From bfb8e95599762f73cc5c8b093932d1750b55c5ea Mon Sep 17 00:00:00 2001 From: f Date: Sun, 6 Mar 2022 12:51:06 -0300 Subject: [PATCH 18/43] obtener el nombre de archivo desde la key esto agrega una query pero permite no tener que agregar una ruta falsa --- .../active_storage/service/jekyll_service.rb | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/app/lib/active_storage/service/jekyll_service.rb b/app/lib/active_storage/service/jekyll_service.rb index 20dda523..02316e65 100644 --- a/app/lib/active_storage/service/jekyll_service.rb +++ b/app/lib/active_storage/service/jekyll_service.rb @@ -10,8 +10,6 @@ module ActiveStorage # implementemos IPFS) para poder transferir los archivos junto con el # sitio. class JekyllService < Service::DiskService - BLOB_NAME = 'blob' - # Genera un servicio para un sitio determinado def self.build_for_site(site:) new(root: File.join(site.path, 'public'), public: true).tap do |js| @@ -19,16 +17,7 @@ module ActiveStorage end end - # Para poder guardar el archivo con el nombre original pero poder - # recuperarlo durante el download, luego de subirlo le cambiamos el - # nombre y creamos un link simbólico a un nombre conocido. - def upload(key, io, checksum: nil, **options) - super.tap do - path = path_for(key) - filename = options[:filename].to_s - FileUtils.mv path, path.sub(/#{BLOB_NAME}\z/, filename) - FileUtils.ln_s filename, path end end @@ -41,9 +30,17 @@ module ActiveStorage key end + # Obtiene el nombre de archivo para esta key + # + # @param :key [String] + # @return [String] + def filename_for(key) + @filename_for ||= ActiveStorage::Blob.where(key: key).limit(1).pluck(:filename).first + end + # Crea una ruta para la llave con un nombre conocido. def path_for(key) - File.join root, folder_for(key), BLOB_NAME + File.join root, folder_for(key), filename_for(key) end end end From 85cab49208cbfae13b1876ab5a57495544b4220c Mon Sep 17 00:00:00 2001 From: f Date: Sun, 6 Mar 2022 15:25:15 -0300 Subject: [PATCH 19/43] subir los archivos desde el editor al sitio --- .../direct_uploads_controller_decorator.rb | 18 ++++++++++++++++++ app/controllers/posts_controller.rb | 6 ++++++ .../active_storage/service/jekyll_service.rb | 4 ---- 3 files changed, 24 insertions(+), 4 deletions(-) create mode 100644 app/controllers/active_storage/direct_uploads_controller_decorator.rb diff --git a/app/controllers/active_storage/direct_uploads_controller_decorator.rb b/app/controllers/active_storage/direct_uploads_controller_decorator.rb new file mode 100644 index 00000000..f27c4cfb --- /dev/null +++ b/app/controllers/active_storage/direct_uploads_controller_decorator.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module ActiveStorage + # Modifica la creación de un blob antes de subir el archivo para que + # incluya el JekyllService adecuado. + module DirectUploadsControllerDecorator + extend ActiveSupport::Concern + + included do + def create + blob = ActiveStorage::Blob.create_before_direct_upload!(service_name: session[:service_name], **blob_args) + render json: direct_upload_json(blob) + end + end + end +end + +ActiveStorage::DirectUploadsController.include ActiveStorage::DirectUploadsControllerDecorator diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index dbdd4d0a..2aff9ac9 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -6,6 +6,7 @@ class PostsController < ApplicationController rescue_from Pundit::NilPolicyError, with: :page_not_found before_action :authenticate_usuarie! + before_action :service_for_direct_upload, only: %i[new edit] # TODO: Traer los comunes desde ApplicationController breadcrumb -> { current_usuarie.email }, :edit_usuarie_registration_path @@ -166,4 +167,9 @@ class PostsController < ApplicationController def post @post ||= site.posts(lang: locale).find(params[:post_id] || params[:id]) end + + # Recuerda el nombre del servicio de subida de archivos + def service_for_direct_upload + session[:service_name] = site.name.to_sym + end end diff --git a/app/lib/active_storage/service/jekyll_service.rb b/app/lib/active_storage/service/jekyll_service.rb index 02316e65..601b6f2f 100644 --- a/app/lib/active_storage/service/jekyll_service.rb +++ b/app/lib/active_storage/service/jekyll_service.rb @@ -17,10 +17,6 @@ module ActiveStorage end end - - end - end - # Mantener retrocompatibilidad con cómo gestionamos los archivos # subidos hasta ahora. # From c13c021fe92d749cf4a98673bc3cc504c6e90d78 Mon Sep 17 00:00:00 2001 From: f Date: Sun, 6 Mar 2022 15:28:25 -0300 Subject: [PATCH 20/43] asignar el nombre de archivo en la subida directa --- .../active_storage/service/jekyll_service.rb | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/app/lib/active_storage/service/jekyll_service.rb b/app/lib/active_storage/service/jekyll_service.rb index 601b6f2f..a2e04336 100644 --- a/app/lib/active_storage/service/jekyll_service.rb +++ b/app/lib/active_storage/service/jekyll_service.rb @@ -17,6 +17,32 @@ module ActiveStorage end end + # Lo mismo que en DiskService agregando el nombre de archivo en la + # firma. Esto permite que luego podamos guardar el archivo donde + # corresponde. + def url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:) + instrument :url, key: key do |payload| + verified_token_with_expiration = ActiveStorage.verifier.generate( + { + key: key, + content_type: content_type, + content_length: content_length, + checksum: checksum, + service_name: name, + filename: filename_for(key) + }, + expires_in: expires_in, + purpose: :blob_token + ) + + generated_url = url_helpers.update_rails_disk_service_url(verified_token_with_expiration, host: current_host) + + payload[:url] = generated_url + + generated_url + end + end + # Mantener retrocompatibilidad con cómo gestionamos los archivos # subidos hasta ahora. # From 211fb308e39ddd1a380b711e98d240c59e9576d6 Mon Sep 17 00:00:00 2001 From: f Date: Sun, 6 Mar 2022 15:59:57 -0300 Subject: [PATCH 21/43] asignar archivos subidos desde el editor al sitio --- .../disk_controller_decorator.rb | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 app/controllers/active_storage/disk_controller_decorator.rb diff --git a/app/controllers/active_storage/disk_controller_decorator.rb b/app/controllers/active_storage/disk_controller_decorator.rb new file mode 100644 index 00000000..14366a15 --- /dev/null +++ b/app/controllers/active_storage/disk_controller_decorator.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module ActiveStorage + # Modificar {DiskController} para poder asociar el blob a un sitio + module DiskControllerDecorator + extend ActiveSupport::Concern + + included do + # Asociar el archivo subido al sitio correspondiente. Cada sitio + # tiene su propio servicio de subida de archivos. + def update + if (token = decode_verified_token) + if acceptable_content?(token) + named_disk_service(token[:service_name]).upload token[:key], request.body, checksum: token[:checksum] + + blob = ActiveStorage::Blob.find_by_key token[:key] + site = Site.find_by_name token[:service_name] + + site.static_files.attach(blob) + else + head :unprocessable_entity + end + else + head :not_found + end + rescue ActiveStorage::IntegrityError + head :unprocessable_entity + end + end + end +end + +ActiveStorage::DiskController.include ActiveStorage::DiskControllerDecorator From 61622a4c416b73288cc897b89b8721fe3bb700c9 Mon Sep 17 00:00:00 2001 From: f Date: Sun, 6 Mar 2022 16:00:21 -0300 Subject: [PATCH 22/43] =?UTF-8?q?documentaci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/lib/active_storage/service/jekyll_service.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/lib/active_storage/service/jekyll_service.rb b/app/lib/active_storage/service/jekyll_service.rb index a2e04336..3b1db7ec 100644 --- a/app/lib/active_storage/service/jekyll_service.rb +++ b/app/lib/active_storage/service/jekyll_service.rb @@ -11,6 +11,9 @@ module ActiveStorage # sitio. class JekyllService < Service::DiskService # Genera un servicio para un sitio determinado + # + # @param :site [Site] + # @return [ActiveStorage::Service::JekyllService] def self.build_for_site(site:) new(root: File.join(site.path, 'public'), public: true).tap do |js| js.name = site.name.to_sym @@ -20,6 +23,13 @@ module ActiveStorage # Lo mismo que en DiskService agregando el nombre de archivo en la # firma. Esto permite que luego podamos guardar el archivo donde # corresponde. + # + # @param :key [String] + # @param :expires_in [Integer] + # @param :content_type [String] + # @param :content_length [Integer] + # @param :checksum [String] + # @return [String] def url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:) instrument :url, key: key do |payload| verified_token_with_expiration = ActiveStorage.verifier.generate( @@ -61,6 +71,9 @@ module ActiveStorage end # Crea una ruta para la llave con un nombre conocido. + # + # @param :key [String] + # @return [String] def path_for(key) File.join root, folder_for(key), filename_for(key) end From 6c9288b03bd64f907760d63a5c0ddd638b9ea74f Mon Sep 17 00:00:00 2001 From: f Date: Sun, 6 Mar 2022 16:01:16 -0300 Subject: [PATCH 23/43] =?UTF-8?q?deprecar=20la=20migraci=C3=B3n=20de=20arc?= =?UTF-8?q?hivos=20est=C3=A1ticos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit siempre fue super lenta y falible --- app/models/site.rb | 7 +--- app/models/site/static_file_migration.rb | 52 ------------------------ 2 files changed, 1 insertion(+), 58 deletions(-) delete mode 100644 app/models/site/static_file_migration.rb diff --git a/app/models/site.rb b/app/models/site.rb index 5b78d625..7d4875e5 100644 --- a/app/models/site.rb +++ b/app/models/site.rb @@ -57,7 +57,7 @@ class Site < ApplicationRecord # Carga el sitio Jekyll una vez que se inicializa el modelo o después # de crearlo after_initialize :load_jekyll - after_create :load_jekyll, :static_file_migration! + after_create :load_jekyll # Cambiar el nombre del directorio before_update :update_name! before_save :add_private_key_if_missing! @@ -474,11 +474,6 @@ class Site < ApplicationRecord config.hostname = hostname end - # Migra los archivos a Sutty - def static_file_migration! - Site::StaticFileMigration.new(site: self).migrate! - end - # Valida si el sitio tiene al menos una forma de alojamiento asociada # y es la local # diff --git a/app/models/site/static_file_migration.rb b/app/models/site/static_file_migration.rb deleted file mode 100644 index 36a882bf..00000000 --- a/app/models/site/static_file_migration.rb +++ /dev/null @@ -1,52 +0,0 @@ -# frozen_string_literal: true - -class Site - # Obtiene todos los archivos relacionados en artículos del sitio y los - # sube a Sutty. - class StaticFileMigration - # Tipos de metadatos que contienen archivos - STATIC_TYPES = %i[file image].freeze - - attr_reader :site - - def initialize(site:) - @site = site - end - - def migrate! - modified = site.docs.map do |doc| - next unless STATIC_TYPES.map do |field| - next unless doc.attribute? field - next unless doc[field].path? - next unless doc[field].static_file - - true - end.any? - - log.write "#{doc.path.relative};no se pudo guardar\n" unless doc.save(validate: false) - - doc.path.absolute - end.compact - - log.close - - return if modified.empty? - - # TODO: Hacer la migración desde el servicio de creación de sitios? - site.repository.commit(file: modified, - message: I18n.t('sites.static_file_migration'), - usuarie: author) - end - - private - - def author - @author ||= GitAuthor.new email: "sutty@#{Site.domain}", - name: 'Sutty' - end - - def log - @log ||= File.open(File.join(site.path, 'migration.csv'), 'w') - end - end -end From f7d8a3ecf9e0142292ea819e7ab94c6b6b096a56 Mon Sep 17 00:00:00 2001 From: f Date: Sun, 6 Mar 2022 17:07:37 -0300 Subject: [PATCH 24/43] fixup! obtener el nombre de archivo desde la key --- app/lib/active_storage/service/jekyll_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/active_storage/service/jekyll_service.rb b/app/lib/active_storage/service/jekyll_service.rb index 3b1db7ec..92b26e0e 100644 --- a/app/lib/active_storage/service/jekyll_service.rb +++ b/app/lib/active_storage/service/jekyll_service.rb @@ -67,7 +67,7 @@ module ActiveStorage # @param :key [String] # @return [String] def filename_for(key) - @filename_for ||= ActiveStorage::Blob.where(key: key).limit(1).pluck(:filename).first + ActiveStorage::Blob.where(key: key).limit(1).pluck(:filename).first end # Crea una ruta para la llave con un nombre conocido. From 81cbafef66e31ba60ac6bf396adc88696d85f142 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 7 Mar 2022 20:52:33 -0300 Subject: [PATCH 25/43] validaciones --- app/models/metadata_file.rb | 2 +- app/models/metadata_image.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/metadata_file.rb b/app/models/metadata_file.rb index 1c859481..5be7f84c 100644 --- a/app/models/metadata_file.rb +++ b/app/models/metadata_file.rb @@ -19,7 +19,7 @@ class MetadataFile < MetadataTemplate errors << I18n.t("metadata.#{type}.site_invalid") if site.invalid? errors << I18n.t("metadata.#{type}.path_required") if path_missing? errors << I18n.t("metadata.#{type}.no_file_for_description") if no_file_for_description? - errors << I18n.t("metadata.#{type}.attachment_missing") unless static_file + errors << I18n.t("metadata.#{type}.attachment_missing") if path? && !static_file errors.compact! errors.empty? diff --git a/app/models/metadata_image.rb b/app/models/metadata_image.rb index f86c5c26..85ee062a 100644 --- a/app/models/metadata_image.rb +++ b/app/models/metadata_image.rb @@ -5,7 +5,7 @@ class MetadataImage < MetadataFile def validate super - errors << I18n.t('metadata.image.not_an_image') unless image? + errors << I18n.t('metadata.image.not_an_image') unless path? && image? errors.compact! errors.empty? From 7de96a4581a1d1483628c3da11f99e1a4e454ec0 Mon Sep 17 00:00:00 2001 From: f Date: Tue, 8 Mar 2022 13:10:34 -0300 Subject: [PATCH 26/43] =?UTF-8?q?usar=20la=20ruta=20absoluta=20tambi=C3=A9?= =?UTF-8?q?n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit en producción usamos links simbólicos entre directorios y sin resolver la ubicación real de los sitios estábamos generando rutas erróneas. --- app/models/metadata_file.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/metadata_file.rb b/app/models/metadata_file.rb index 5be7f84c..31becc64 100644 --- a/app/models/metadata_file.rb +++ b/app/models/metadata_file.rb @@ -119,7 +119,7 @@ class MetadataFile < MetadataTemplate end def relative_destination_path_with_filename - destination_path_with_filename.relative_path_from(site.path) + destination_path_with_filename.relative_path_from(Pathname.new(site.path).realpath) end def static_file_path From 79974b710497c2db0498de7f9f4375f2802711c9 Mon Sep 17 00:00:00 2001 From: f Date: Tue, 8 Mar 2022 13:21:13 -0300 Subject: [PATCH 27/43] =?UTF-8?q?validar=20im=C3=A1genes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/metadata_image.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/metadata_image.rb b/app/models/metadata_image.rb index 85ee062a..fc61a4d7 100644 --- a/app/models/metadata_image.rb +++ b/app/models/metadata_image.rb @@ -5,7 +5,7 @@ class MetadataImage < MetadataFile def validate super - errors << I18n.t('metadata.image.not_an_image') unless path? && image? + errors << I18n.t('metadata.image.not_an_image') if path? && !image? errors.compact! errors.empty? From d4d1acc8d91e5cf23db4fa056a5c0520d7179c43 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 9 Mar 2022 19:32:53 -0300 Subject: [PATCH 28/43] retrocompatibilidad los archivos subidos como archivos locales ya estaban copiados al sitio --- app/models/metadata_file.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/models/metadata_file.rb b/app/models/metadata_file.rb index 31becc64..0f50d643 100644 --- a/app/models/metadata_file.rb +++ b/app/models/metadata_file.rb @@ -123,7 +123,12 @@ class MetadataFile < MetadataTemplate end def static_file_path - static_file.blob.service.path_for(static_file.key) + case static_file.blob.service.name + when :local + File.join(site.path, 'public', static_file.key, static_file.filename.to_s) + else + static_file.blob.service.path_for(static_file.key) + end end # No hay archivo pero se lo describió From 263394baef931995b78e4a962a6bebfbc5f76b27 Mon Sep 17 00:00:00 2001 From: f Date: Fri, 18 Mar 2022 15:42:39 -0300 Subject: [PATCH 29/43] no fallar al serializar Site#everything_of el problema era que los archivos subidos no se pueden marshalear y estamos usando MetadataTemplate#values para poder cachearlos. closes #1315 closes #1316 closes #1842 closes #2368 closes #2369 closes #2370 closes #2371 closes #2372 closes #2373 closes #2393 closes #2396 closes #2922 closes #3347 closes #3350 closes #3353 closes #3356 closes #3437 closes #3486 closes #3487 closes #3488 closes #3551 closes #3554 closes #4229 closes #4256 closes #4259 closes #4262 closes #4265 closes #4553 closes #4556 closes #4603 closes #4606 closes #4647 closes #4759 closes #4767 closes #4787 closes #4794 closes #4833 closes #4835 closes #4836 closes #4875 closes #4876 closes #4877 closes #4879 closes #4881 closes #4882 closes #4883 closes #4884 closes #4885 closes #4886 closes #4887 closes #4888 closes #4889 closes #4890 closes #4891 closes #4892 closes #4906 closes #4907 closes #4908 closes #4976 closes #4977 closes #4978 closes #4979 closes #4980 closes #4981 closes #4982 closes #4983 closes #4984 closes #4985 closes #4986 closes #4987 --- app/models/metadata_file.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/models/metadata_file.rb b/app/models/metadata_file.rb index 0f50d643..2c1a6d59 100644 --- a/app/models/metadata_file.rb +++ b/app/models/metadata_file.rb @@ -13,6 +13,11 @@ class MetadataFile < MetadataTemplate value == default_value end + # No hay valores sugeridos para archivos subidos. + # + # XXX: Esto ayuda a deserializar en {Site#everything_of} + def values; end + def validate super From c5896a63cfdd48128592087aac35aaf1a6523df1 Mon Sep 17 00:00:00 2001 From: f Date: Tue, 22 Mar 2022 20:15:59 -0300 Subject: [PATCH 30/43] resiliencia closes #5061 closes #5060 closes #5058 closes #5057 closes #5056 closes #5054 closes #5053 closes #5051 closes #5050 closes #5048 closes #5047 closes #5045 closes #5042 closes #5040 closes #5038 closes #5036 closes #5034 closes #5033 closes #5032 closes #5030 closes #4946 closes #3856 closes #3562 closes #3558 closes #3557 closes #2707 closes #2706 closes #2705 closes #2703 closes #2702 closes #2297 closes #2296 closes #2295 closes #1970 closes #1969 closes #1768 --- app/models/metadata_file.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/models/metadata_file.rb b/app/models/metadata_file.rb index 2c1a6d59..eca87478 100644 --- a/app/models/metadata_file.rb +++ b/app/models/metadata_file.rb @@ -121,6 +121,13 @@ class MetadataFile < MetadataTemplate # @return [Pathname] def destination_path_with_filename destination_path.realpath + # Si el archivo no llegara a existir, en lugar de hacer fallar todo, + # devolvemos la ruta original, que puede ser el archivo que no existe + # o vacía si se está subiendo uno. + rescue Errno::ENOENT => e + ExceptionNotifier.notify_exception(e) + + value['path'] end def relative_destination_path_with_filename From ecb823f40746b4eae0d61cc3acca63ac620881f0 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 23 Mar 2022 11:37:00 -0300 Subject: [PATCH 31/43] al migrar un archivo reutilizar la key --- app/models/metadata_file.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/models/metadata_file.rb b/app/models/metadata_file.rb index eca87478..71d3f049 100644 --- a/app/models/metadata_file.rb +++ b/app/models/metadata_file.rb @@ -78,7 +78,9 @@ class MetadataFile < MetadataTemplate if (blob_id = ActiveStorage::Blob.where(key: key_from_path).pluck(:id).first) site.static_files.find_by(blob_id: blob_id) elsif path? && pathname.exist? && site.static_files.attach(io: pathname.open, filename: pathname.basename) - site.static_files.last + site.static_files.last.tap do |s| + s.blob.update(key: key_from_path) + end end end end From 455070f2ea576775fe28f0bb2d01a6b62d2dc7d0 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 4 Apr 2022 13:56:40 -0300 Subject: [PATCH 32/43] normalizar todas las strings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit convierte la codificación de windows/osx en unicode normalizado. --- app/models/metadata_markdown.rb | 2 +- app/models/metadata_markdown_content.rb | 2 +- app/models/metadata_permalink.rb | 2 +- app/models/metadata_string.rb | 2 +- app/models/metadata_template.rb | 9 ++++++--- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/app/models/metadata_markdown.rb b/app/models/metadata_markdown.rb index 1e8b4fc8..7816ec33 100644 --- a/app/models/metadata_markdown.rb +++ b/app/models/metadata_markdown.rb @@ -12,6 +12,6 @@ class MetadataMarkdown < MetadataText # markdown y se eliminan autolinks. Mejor es habilitar la generación # SAFE de CommonMark en la configuración del sitio. def sanitize(string) - string + string.unicode_normalize(:nfkc) end end diff --git a/app/models/metadata_markdown_content.rb b/app/models/metadata_markdown_content.rb index 92a1ab21..cb4124db 100644 --- a/app/models/metadata_markdown_content.rb +++ b/app/models/metadata_markdown_content.rb @@ -25,6 +25,6 @@ class MetadataMarkdownContent < MetadataText # markdown y se eliminan autolinks. Mejor es deshabilitar la # generación SAFE de CommonMark en la configuración del sitio. def sanitize(string) - string.tr("\r", '') + string.tr("\r", '').unicode_normalize(:nfkc) end end diff --git a/app/models/metadata_permalink.rb b/app/models/metadata_permalink.rb index 59b68461..9b0c063c 100644 --- a/app/models/metadata_permalink.rb +++ b/app/models/metadata_permalink.rb @@ -19,7 +19,7 @@ class MetadataPermalink < MetadataString # puntos suspensivos, la primera / para que siempre sea relativa y # agregamos una / al final si la ruta no tiene extensión. def sanitize(value) - value = value.strip.gsub('..', '/').gsub('./', '').squeeze('/') + value = value.strip.unicode_normalize(:nfkc).gsub('..', '/').gsub('./', '').squeeze('/') value = value[1..-1] if value.start_with? '/' value += '/' if File.extname(value).blank? diff --git a/app/models/metadata_string.rb b/app/models/metadata_string.rb index 95aac4d4..28bfe82a 100644 --- a/app/models/metadata_string.rb +++ b/app/models/metadata_string.rb @@ -17,7 +17,7 @@ class MetadataString < MetadataTemplate def sanitize(string) return '' if string.blank? - sanitizer.sanitize(string.strip, + sanitizer.sanitize(string.strip.unicode_normalize(:nfkc), tags: [], attributes: []).strip.html_safe end diff --git a/app/models/metadata_template.rb b/app/models/metadata_template.rb index 5baa7a4a..a72f8e83 100644 --- a/app/models/metadata_template.rb +++ b/app/models/metadata_template.rb @@ -184,9 +184,12 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type, return if string.nil? return string unless string.is_a? String - sanitizer.sanitize(string.tr("\r", ''), - tags: allowed_tags, - attributes: allowed_attributes).strip.html_safe + sanitizer + .sanitize(string.tr("\r", '').unicode_normalize(:nfkc), + tags: allowed_tags, + attributes: allowed_attributes) + .strip + .html_safe end def sanitizer From 30bc14d83e790c6540a6fb19e8afa8184eef6c37 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 4 Apr 2022 14:27:00 -0300 Subject: [PATCH 33/43] normalizar los nombres de archivo enviados por subida directa --- .../direct_uploads_controller_decorator.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/controllers/active_storage/direct_uploads_controller_decorator.rb b/app/controllers/active_storage/direct_uploads_controller_decorator.rb index f27c4cfb..3052f974 100644 --- a/app/controllers/active_storage/direct_uploads_controller_decorator.rb +++ b/app/controllers/active_storage/direct_uploads_controller_decorator.rb @@ -11,6 +11,17 @@ module ActiveStorage blob = ActiveStorage::Blob.create_before_direct_upload!(service_name: session[:service_name], **blob_args) render json: direct_upload_json(blob) end + + private + + # Normalizar los caracteres unicode en los nombres de archivos + # para que puedan propagarse correctamente a través de todo el + # stack. + def blob_args + params.require(:blob).permit(:filename, :byte_size, :checksum, :content_type, metadata: {}).to_h.symbolize_keys.tap do |ba| + ba[:filename] = ba[:filename].unicode_normalize(:nfkc) + end + end end end end From 4d6a26d67178ec90e4b8f580889cee528f74d195 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 4 Apr 2022 14:32:58 -0300 Subject: [PATCH 34/43] normalizar todos los nombres de archivos subidos --- .../http/uploaded_file_decorator.rb | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 app/lib/action_dispatch/http/uploaded_file_decorator.rb diff --git a/app/lib/action_dispatch/http/uploaded_file_decorator.rb b/app/lib/action_dispatch/http/uploaded_file_decorator.rb new file mode 100644 index 00000000..c171c81c --- /dev/null +++ b/app/lib/action_dispatch/http/uploaded_file_decorator.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module ActionDispatch + module Http + # Normaliza los nombres de archivo para que se propaguen + # correctamente a través de todo el stack. + module UploadedFileDecorator + extend ActiveSupport::Concern + + included do + # Devolver el nombre de archivo con caracteres unicode + # normalizados + def original_filename + @original_filename.unicode_normalize(:nfkc) + end + end + end + end +end + +ActionDispatch::Http::UploadedFile.include ActionDispatch::Http::UploadedFileDecorator From 13561a5f717af796371a244daf11b2e7d3d029d7 Mon Sep 17 00:00:00 2001 From: f Date: Tue, 19 Apr 2022 10:07:10 -0300 Subject: [PATCH 35/43] =?UTF-8?q?no=20generar=20errores=20si=20se=20env?= =?UTF-8?q?=C3=ADa=20el=20reporte=20incompleto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit closes #5136 --- app/controllers/api/v1/csp_reports_controller.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/v1/csp_reports_controller.rb b/app/controllers/api/v1/csp_reports_controller.rb index bc6cfae0..f1d7a376 100644 --- a/app/controllers/api/v1/csp_reports_controller.rb +++ b/app/controllers/api/v1/csp_reports_controller.rb @@ -6,6 +6,9 @@ module Api class CspReportsController < BaseController skip_forgery_protection + # No queremos indicar que algo salió mal + rescue_from ActionController::ParameterMissing, with: :csp_report_created + # Crea un reporte de CSP intercambiando los guiones medios por # bajos # @@ -18,7 +21,7 @@ module Api csp.id = SecureRandom.uuid csp.save - render json: {}, status: :created + csp_report_created end private @@ -39,6 +42,10 @@ module Api :'column-number', :'source-file') end + + def csp_report_created + render json: {}, status: :created + end end end end From 52f446fcf1e8fc9c31a1ee9ef1028f320bb24304 Mon Sep 17 00:00:00 2001 From: f Date: Tue, 19 Apr 2022 10:57:01 -0300 Subject: [PATCH 36/43] refactorizacion --- app/controllers/api/v1/csp_reports_controller.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/api/v1/csp_reports_controller.rb b/app/controllers/api/v1/csp_reports_controller.rb index f1d7a376..ea186729 100644 --- a/app/controllers/api/v1/csp_reports_controller.rb +++ b/app/controllers/api/v1/csp_reports_controller.rb @@ -14,9 +14,9 @@ module Api # # TODO: Aplicar rate_limit def create - csp = CspReport.new(csp_report_params.to_h.map do |k, v| - [k.tr('-', '_'), v] - end.to_h) + csp = CspReport.new(csp_report_params.to_h.transform_keys do |k| + k.tr('-', '_') + end) csp.id = SecureRandom.uuid csp.save From eb7d0679bbc411d8f2afe831a23d958bc0e91abd Mon Sep 17 00:00:00 2001 From: f Date: Thu, 28 Apr 2022 10:34:57 -0300 Subject: [PATCH 37/43] normalizar strings sin romperlas MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit en castellano no nos afectó pero en árabe rompe el significado --- .../active_storage/direct_uploads_controller_decorator.rb | 2 +- app/lib/action_dispatch/http/uploaded_file_decorator.rb | 2 +- app/models/metadata_markdown.rb | 2 +- app/models/metadata_markdown_content.rb | 2 +- app/models/metadata_permalink.rb | 2 +- app/models/metadata_string.rb | 2 +- app/models/metadata_template.rb | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/controllers/active_storage/direct_uploads_controller_decorator.rb b/app/controllers/active_storage/direct_uploads_controller_decorator.rb index 3052f974..c62dae2a 100644 --- a/app/controllers/active_storage/direct_uploads_controller_decorator.rb +++ b/app/controllers/active_storage/direct_uploads_controller_decorator.rb @@ -19,7 +19,7 @@ module ActiveStorage # stack. def blob_args params.require(:blob).permit(:filename, :byte_size, :checksum, :content_type, metadata: {}).to_h.symbolize_keys.tap do |ba| - ba[:filename] = ba[:filename].unicode_normalize(:nfkc) + ba[:filename] = ba[:filename].unicode_normalize end end end diff --git a/app/lib/action_dispatch/http/uploaded_file_decorator.rb b/app/lib/action_dispatch/http/uploaded_file_decorator.rb index c171c81c..0bdebdc0 100644 --- a/app/lib/action_dispatch/http/uploaded_file_decorator.rb +++ b/app/lib/action_dispatch/http/uploaded_file_decorator.rb @@ -11,7 +11,7 @@ module ActionDispatch # Devolver el nombre de archivo con caracteres unicode # normalizados def original_filename - @original_filename.unicode_normalize(:nfkc) + @original_filename.unicode_normalize end end end diff --git a/app/models/metadata_markdown.rb b/app/models/metadata_markdown.rb index 7816ec33..a09e351c 100644 --- a/app/models/metadata_markdown.rb +++ b/app/models/metadata_markdown.rb @@ -12,6 +12,6 @@ class MetadataMarkdown < MetadataText # markdown y se eliminan autolinks. Mejor es habilitar la generación # SAFE de CommonMark en la configuración del sitio. def sanitize(string) - string.unicode_normalize(:nfkc) + string.unicode_normalize end end diff --git a/app/models/metadata_markdown_content.rb b/app/models/metadata_markdown_content.rb index cb4124db..75088e30 100644 --- a/app/models/metadata_markdown_content.rb +++ b/app/models/metadata_markdown_content.rb @@ -25,6 +25,6 @@ class MetadataMarkdownContent < MetadataText # markdown y se eliminan autolinks. Mejor es deshabilitar la # generación SAFE de CommonMark en la configuración del sitio. def sanitize(string) - string.tr("\r", '').unicode_normalize(:nfkc) + string.tr("\r", '').unicode_normalize end end diff --git a/app/models/metadata_permalink.rb b/app/models/metadata_permalink.rb index 9b0c063c..30ad32cc 100644 --- a/app/models/metadata_permalink.rb +++ b/app/models/metadata_permalink.rb @@ -19,7 +19,7 @@ class MetadataPermalink < MetadataString # puntos suspensivos, la primera / para que siempre sea relativa y # agregamos una / al final si la ruta no tiene extensión. def sanitize(value) - value = value.strip.unicode_normalize(:nfkc).gsub('..', '/').gsub('./', '').squeeze('/') + value = value.strip.unicode_normalize.gsub('..', '/').gsub('./', '').squeeze('/') value = value[1..-1] if value.start_with? '/' value += '/' if File.extname(value).blank? diff --git a/app/models/metadata_string.rb b/app/models/metadata_string.rb index 28bfe82a..c1d888b1 100644 --- a/app/models/metadata_string.rb +++ b/app/models/metadata_string.rb @@ -17,7 +17,7 @@ class MetadataString < MetadataTemplate def sanitize(string) return '' if string.blank? - sanitizer.sanitize(string.strip.unicode_normalize(:nfkc), + sanitizer.sanitize(string.strip.unicode_normalize, tags: [], attributes: []).strip.html_safe end diff --git a/app/models/metadata_template.rb b/app/models/metadata_template.rb index a72f8e83..ddcd100e 100644 --- a/app/models/metadata_template.rb +++ b/app/models/metadata_template.rb @@ -185,7 +185,7 @@ MetadataTemplate = Struct.new(:site, :document, :name, :label, :type, return string unless string.is_a? String sanitizer - .sanitize(string.tr("\r", '').unicode_normalize(:nfkc), + .sanitize(string.tr("\r", '').unicode_normalize, tags: allowed_tags, attributes: allowed_attributes) .strip From f4ec30eac7c41d7ef189768cd929ac3928a7d184 Mon Sep 17 00:00:00 2001 From: f Date: Thu, 28 Apr 2022 12:06:27 -0300 Subject: [PATCH 38/43] jekyll no soporta urls con barra al final MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit y genera un montón de problemas! --- app/models/site.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/site.rb b/app/models/site.rb index 5b78d625..af10b004 100644 --- a/app/models/site.rb +++ b/app/models/site.rb @@ -470,7 +470,7 @@ class Site < ApplicationRecord config.theme = design.gem unless design.no_theme? config.description = description config.title = title - config.url = url + config.url = url(slash: false) config.hostname = hostname end From 6bbb5b48c0c6f63fa3ee2601affcdd51d94cb79e Mon Sep 17 00:00:00 2001 From: Nulo Date: Sat, 30 Apr 2022 14:56:04 +0000 Subject: [PATCH 39/43] editor: Borrar comentario viejo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ahora la blockquote está implementada por https://0xacab.org/sutty/sutty/-/merge_requests/83, está implementada como bloque normal ya que una blockquote puede tener varios blockquotes adentro --- app/javascript/editor/types/parentBlocks.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/app/javascript/editor/types/parentBlocks.ts b/app/javascript/editor/types/parentBlocks.ts index ffe40bdf..5ba8f366 100644 --- a/app/javascript/editor/types/parentBlocks.ts +++ b/app/javascript/editor/types/parentBlocks.ts @@ -20,7 +20,6 @@ function makeParentBlock( }; } -// TODO: añadir blockquote // XXX: si agregás algo acá, probablemente le quieras hacer un botón // en app/views/posts/attributes/_content.haml export const parentBlocks: { [propName: string]: EditorNode } = { From 24c0161aac6451afc65f7f222bc4acbe00b11d2e Mon Sep 17 00:00:00 2001 From: f Date: Wed, 29 Jun 2022 18:17:23 -0300 Subject: [PATCH 40/43] =?UTF-8?q?no=20fallar=20si=20los=20layouts=20no=20t?= =?UTF-8?q?ienen=20traducci=C3=B3n=20#6841?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/site.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/models/site.rb b/app/models/site.rb index 7d4875e5..58947263 100644 --- a/app/models/site.rb +++ b/app/models/site.rb @@ -166,8 +166,10 @@ class Site < ApplicationRecord end # Similar a site.i18n en jekyll-locales + # + # @return [Hash] def i18n - data[I18n.locale.to_s] + data[I18n.locale.to_s] || {} end # Devuelve el idioma por defecto del sitio, el primero de la lista. From 2a416b4e670fc8690538a2de70d501b72e0ee436 Mon Sep 17 00:00:00 2001 From: Nulo Date: Wed, 13 Jul 2022 21:43:44 +0000 Subject: [PATCH 41/43] editor: No reportar error cuando no se selecciona un archivo para subir --- app/javascript/editor/types/multimedia.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/javascript/editor/types/multimedia.ts b/app/javascript/editor/types/multimedia.ts index 2af9643a..29d2a0ab 100644 --- a/app/javascript/editor/types/multimedia.ts +++ b/app/javascript/editor/types/multimedia.ts @@ -137,8 +137,10 @@ export function setupAuxiliaryToolbar(editor: Editor): void { "click", (event) => { const files = editor.toolbar.auxiliary.multimedia.fileEl.files; - if (!files || !files.length) - throw new Error("no hay archivos para subir"); + if (!files || !files.length) { + console.info("no hay archivos para subir"); + return; + } const file = files[0]; const selectedEl = editor.contentEl.querySelector( From 69712a306e131e48f6c7416002e2e8f1f1cee515 Mon Sep 17 00:00:00 2001 From: f Date: Mon, 25 Jul 2022 16:01:08 -0300 Subject: [PATCH 42/43] equiparar file con image #7076 --- app/views/posts/attributes/_file.haml | 16 +++++++++------- app/views/posts/attributes/_image.haml | 3 +-- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/app/views/posts/attributes/_file.haml b/app/views/posts/attributes/_file.haml index 19175b9c..54f9f81a 100644 --- a/app/views/posts/attributes/_file.haml +++ b/app/views/posts/attributes/_file.haml @@ -1,7 +1,5 @@ .form-group - if metadata.static_file - = hidden_field_tag "#{base}[#{attribute}][path]", metadata.value['path'] - - case metadata.static_file.blob.content_type - when %r{\Avideo/} = video_tag url_for(metadata.static_file), @@ -14,13 +12,17 @@ - else = link_to t('posts.attribute_ro.file.download'), url_for(metadata.static_file) - .custom-control.custom-switch - = check_box_tag "#{base}[#{attribute}][path]", '', false, id: "#{base}_#{attribute}_destroy", class: 'custom-control-input' - = label_tag "#{base}_#{attribute}_destroy", t('posts.attributes.file.destroy'), class: 'custom-control-label' + -# Mantener el valor si no enviamos ninguna imagen + = hidden_field_tag "#{base}[#{attribute}][path]", metadata.value['path'] + -# Los archivos requeridos solo se pueden reemplazar + - unless metadata.required + .custom-control.custom-switch + = check_box_tag "#{base}[#{attribute}][path]", '', false, id: "#{base}_#{attribute}_destroy", class: 'custom-control-input' + = label_tag "#{base}_#{attribute}_destroy", t('posts.attributes.file.destroy'), class: 'custom-control-label' .custom-file = file_field(*field_name_for(base, attribute, :path), - **field_options(attribute, metadata), + **field_options(attribute, metadata, required: (metadata.required && !metadata.path?)), class: "custom-file-input #{invalid(post, attribute)}", data: { preview: "#{attribute}-preview" }) = label_tag "#{base}_#{attribute}_path", @@ -30,7 +32,7 @@ .form-group = label_tag "#{base}_#{attribute}_description", - post_label_t(attribute, :description, post: post) + post_label_t(attribute, :description, post: post, required: false) = text_field(*field_name_for(base, attribute, :description), value: metadata.value['description'], dir: dir, lang: locale, diff --git a/app/views/posts/attributes/_image.haml b/app/views/posts/attributes/_image.haml index c11f4196..f4d9bb3d 100644 --- a/app/views/posts/attributes/_image.haml +++ b/app/views/posts/attributes/_image.haml @@ -1,5 +1,5 @@ .form-group - - if metadata.uploaded? + - if metadata.static_file = image_tag url_for(metadata.static_file), alt: metadata.value['description'], class: 'img-fluid', @@ -37,4 +37,3 @@ **field_options(attribute, metadata, required: false)) = render 'posts/attribute_feedback', post: post, attribute: [attribute, :description], metadata: metadata - From 64e93506b77de088f3696a1588e5dfad2873bed8 Mon Sep 17 00:00:00 2001 From: f Date: Wed, 3 Aug 2022 12:53:50 -0300 Subject: [PATCH 43/43] =?UTF-8?q?eliminar=20c=C3=B3digo=20que=20no=20se=20?= =?UTF-8?q?usa=20m=C3=A1s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/post/template_field.rb | 360 ------------------------------ 1 file changed, 360 deletions(-) delete mode 100644 app/models/post/template_field.rb diff --git a/app/models/post/template_field.rb b/app/models/post/template_field.rb deleted file mode 100644 index 08da933b..00000000 --- a/app/models/post/template_field.rb +++ /dev/null @@ -1,360 +0,0 @@ -# frozen_string_literal: true - -# Representa los distintos tipos de campos que pueden venir de una -# plantilla compleja -class Post - class TemplateField - attr_reader :post, :contents, :key - - STRING_VALUES = %w[string text url number email password date year - image video audio document].freeze - - # Tipo de valores que son archivos - FILE_TYPES = %w[image video audio document].freeze - - def initialize(post, key, contents) - @post = post - @key = key - @contents = contents - end - - def title - contents.dig('title') if complex? - end - - def subtitle - contents.dig('subtitle') if complex? - end - - # Obtiene el valor - def value - complex? ? contents.dig('value') : contents - end - - def max - return 0 if simple? - - contents.fetch('max', 0) - end - - def min - return 0 if simple? - - contents.fetch('min', 0) - end - - # TODO: volver elegante! - def type - return @type if @type - - if image? - @type = 'image' - elsif email? - @type = 'email' - elsif url? - @type = 'url' - elsif number? - @type = 'number' - elsif password? - @type = 'password' - elsif date? - @type = 'date' - elsif year? - @type = 'year' - elsif text_area? - @type = 'text_area' - elsif check_box_group? - @type = 'check_box_group' - elsif radio_group? - @type = 'radio_group' - elsif string? - @type = 'text' - # TODO: volver a hacer funcionar esto y ahorranos los multiple: - # false - elsif string? && contents.split('/', 2).count == 2 - @type = 'select' - elsif nested? - @type = 'table' - elsif array? - @type = 'select' - elsif boolean? - @type = 'check_box' - end - - @type - end - - # Devuelve los valores vacíos según el tipo - def empty_value - if string? - '' - elsif nested? - # TODO: devolver las keys también - {} - elsif array? - [] - elsif boolean? - false - end - end - - def cols - complex? && contents.dig('cols') - end - - def align - complex? && contents.dig('align') - end - - # El campo es requerido si es complejo y se especifica que lo sea - def required? - complex? && contents.dig('required') - end - - def boolean? - value.is_a?(FalseClass) || value.is_a?(TrueClass) - end - - def string? - value.is_a? String - end - - def text_area? - value == 'text' - end - - def url? - value == 'url' - end - - def email? - value == 'email' || value == 'mail' - end - alias mail? email? - - def date? - value == 'date' - end - - def password? - value == 'password' - end - - def number? - value == 'number' - end - - def year? - value == 'year' - end - - def file? - string? && FILE_TYPES.include?(value) - end - - def image? - array? ? value.first == 'image' : value == 'image' - end - - # Si la plantilla es simple no está admitiendo Hashes como valores - def simple? - !complex? - end - - def complex? - contents.is_a? Hash - end - - # XXX Retrocompatibilidad - def to_s - key - end - - # Convierte el campo en un parámetro - def to_param - if nested? - { key.to_sym => {} } - elsif array? && multiple? - { key.to_sym => [] } - else - key.to_sym - end - end - - # Convierte la plantilla en el formato de front_matter - def to_front_matter - { key => empty_value } - end - - def check_box_group? - array? && (complex? && contents.fetch('checkbox', false)) - end - - def radio_group? - array? && (complex? && contents.fetch('radio', false)) - end - - def array? - value.is_a? Array - end - - # TODO: detectar cuando es complejo y tomar el valor de :multiple - def multiple? - # si la plantilla es simple, es multiple cuando tenemos un array - return array? if simple? - - array? && contents.fetch('multiple', true) - end - - # Detecta si el valor es una tabla de campos - def nested? - value.is_a?(Hash) || (array? && value.first.is_a?(Hash)) - end - - # Un campo acepta valores abiertos si no es un array con múltiples - # elementos - def open? - # Todos los valores simples son abiertos - return true unless complex? - return false unless array? - - # La cosa se complejiza cuando tenemos valores complejos - # - # Si tenemos una lista cerrada de valores, necesitamos saber si el - # campo es abierto o cerrado. Si la lista tiene varios elementos, - # es una lista cerrada, opcionalmente abierta. Si la lista tiene - # un elemento, quiere decir que estamos autocompletando desde otro - # lado. - contents.fetch('open', value.count < 2) - end - - def closed? - !open? - end - - # Determina si los valores del campo serán públicos después - # - # XXX Esto es solo una indicación, el theme Jekyll tiene que - # respetarlos por su lado luego - def public? - # Todos los campos son públicos a menos que se indique lo - # contrario - simple? || contents.fetch('public', true) - end - - def private? - !public? - end - - def human - h = key.humanize - - h - end - - def label - h = (complex? && contents.dig('label')) || human - h += ' *' if required? - - h - end - - def help - complex? && contents.dig('help') - end - - def nested_fields - return unless nested? - - v = value - v = value.first if array? - - @nested_fields ||= v.map do |k, sv| - Post::TemplateField.new post, k, sv - end - end - - # Obtiene los valores posibles para el campo de la plantilla - def values - return 'false' if value == false - return 'true' if value == true - # XXX por alguna razón `value` no refiere a value() :/ - return '' if STRING_VALUES.include? value - # Las listas cerradas no necesitan mayor procesamiento - return value if array? && closed? && value.count > 1 - # Y las vacías tampoco - return value if array? && value.empty? - # Ahorrarnos el trabajo - return @values if @values - - # Duplicar el valor para no tener efectos secundarios luego (?) - value = self.value.dup - - # Para obtener los valores posibles, hay que procesar la string y - # convertirla a parametros - - # Si es una array de un solo elemento, es un indicador de que - # tenemos que rellenarla con los valores que indica. - # - # El primer valor es el que trae la string de autocompletado - values = array? ? value.shift : value - - # Si el valor es un array con más de un elemento, queremos usar - # esas opciones. Pero si además es abierto, queremos traer los - # valores cargados anteriormente. - - # Procesamos el valor, buscando : como separador de campos que - # queremos encontrar y luego los unimos - _value = (values&.split(':', 2) || []).map do |v| - # Tenemos hasta tres niveles de búsqueda - collection, attr, subattr = v.split('/', 3) - - if collection == 'site' - # TODO: puede ser peligroso permitir acceder a cualquier - # atributo de site? No estamos trayendo nada fuera de - # lo normal - post.site.send(attr.to_sym) - # Si hay un subatributo, tenemos que averiguar todos los - # valores dentro de el - # TODO volver elegante! - # TODO volver recursivo! - elsif subattr - post.site.everything_of(attr, lang: collection) - .compact - .map { |sv| sv[subattr] } - .flatten - .compact - .uniq - else - post.site.everything_of(attr, lang: collection).compact - end - end - - # Si el valor es abierto, sumar los valores auto-completados a - # lo pre-cargados. - # - # En este punto _value es un array de 1 o 2 arrays, si es de uno, - # value tambien tiene que serlo. Si es de 2, hay que unir cada - # una - if open? - if _value.count == 1 - _value = [(_value.first + value).uniq] - elsif _value.count == 2 - _value = _value.each_with_index.map do |v, i| - v + value.fetch(i, []) - end - end - end - - # Crea un array de arrays, útil para los select - # [ [ 1, a ], [ 2, b ] ] - # aunque si no hay un : en el autocompletado, el array queda - # [ [ 1, 1 ], [ 2, 2 ] ] - values = _value.empty? ? [] : _value.last.zip(_value.first) - - # En última instancia, traer el valor por defecto y ahorrarnos - # volver a procesar - @values = values - end - end -end