migración de archivos estáticos
This commit is contained in:
parent
0a6b07a4f4
commit
11b347e4c0
9 changed files with 186 additions and 8 deletions
|
@ -56,6 +56,8 @@ class MetadataFile < MetadataTemplate
|
||||||
#
|
#
|
||||||
# @return ActiveStorage::Attachment
|
# @return ActiveStorage::Attachment
|
||||||
def static_file
|
def static_file
|
||||||
|
return @static_file if @static_file
|
||||||
|
|
||||||
ActiveRecord::Base.connection_pool.with_connection do
|
ActiveRecord::Base.connection_pool.with_connection do
|
||||||
if uploaded?
|
if uploaded?
|
||||||
blob = ActiveStorage::Blob.find_by(key: key_from_path)
|
blob = ActiveStorage::Blob.find_by(key: key_from_path)
|
||||||
|
@ -68,10 +70,12 @@ class MetadataFile < MetadataTemplate
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def path
|
||||||
|
@path ||= Pathname.new value['path']
|
||||||
|
end
|
||||||
|
|
||||||
def key_from_path
|
def key_from_path
|
||||||
# XXX: No podemos usar self#extension porque en este punto todavía
|
path.dirname.basename.to_s
|
||||||
# no sabemos el static_file
|
|
||||||
File.basename(value['path'], '.*')
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Hacemos un link duro para colocar el archivo dentro del repositorio
|
# Hacemos un link duro para colocar el archivo dentro del repositorio
|
||||||
|
@ -83,7 +87,7 @@ class MetadataFile < MetadataTemplate
|
||||||
end
|
end
|
||||||
|
|
||||||
def extension
|
def extension
|
||||||
static_file.blob.content_type.split('/').last
|
@extension ||= static_file.filename.to_s.split('.').last
|
||||||
end
|
end
|
||||||
|
|
||||||
# Obtener la ruta al archivo
|
# Obtener la ruta al archivo
|
||||||
|
@ -97,7 +101,7 @@ class MetadataFile < MetadataTemplate
|
||||||
end
|
end
|
||||||
|
|
||||||
def relative_destination_path
|
def relative_destination_path
|
||||||
File.join('public', [static_file.key, extension].join('.'))
|
File.join('public', static_file.key, static_file.filename.to_s)
|
||||||
end
|
end
|
||||||
|
|
||||||
def destination_path
|
def destination_path
|
||||||
|
|
|
@ -156,8 +156,8 @@ class Post < OpenStruct
|
||||||
|
|
||||||
# Guarda los cambios
|
# Guarda los cambios
|
||||||
# rubocop:disable Metrics/CyclomaticComplexity
|
# rubocop:disable Metrics/CyclomaticComplexity
|
||||||
def save
|
def save(validation = true)
|
||||||
return false unless valid?
|
return false if validation && !valid?
|
||||||
# Salir si tenemos que cambiar el nombre del archivo y no pudimos
|
# Salir si tenemos que cambiar el nombre del archivo y no pudimos
|
||||||
return false if !new? && path_changed? && !update_path!
|
return false if !new? && path_changed? && !update_path!
|
||||||
return false unless save_attributes!
|
return false unless save_attributes!
|
||||||
|
|
|
@ -39,7 +39,7 @@ class Site < ApplicationRecord
|
||||||
# Carga el sitio Jekyll una vez que se inicializa el modelo o después
|
# Carga el sitio Jekyll una vez que se inicializa el modelo o después
|
||||||
# de crearlo
|
# de crearlo
|
||||||
after_initialize :load_jekyll
|
after_initialize :load_jekyll
|
||||||
after_create :load_jekyll
|
after_create :load_jekyll, :static_file_migration!
|
||||||
# Cambiar el nombre del directorio
|
# Cambiar el nombre del directorio
|
||||||
before_update :update_name!
|
before_update :update_name!
|
||||||
# Guardar la configuración si hubo cambios
|
# Guardar la configuración si hubo cambios
|
||||||
|
@ -323,6 +323,11 @@ class Site < ApplicationRecord
|
||||||
config.url = url
|
config.url = url
|
||||||
end
|
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
|
# Valida si el sitio tiene al menos una forma de alojamiento asociada
|
||||||
# y es la local
|
# y es la local
|
||||||
#
|
#
|
||||||
|
|
124
app/models/site/static_file_migration.rb
Normal file
124
app/models/site/static_file_migration.rb
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Site
|
||||||
|
MigrationAuthor = Struct.new :email, :name, keyword_init: true
|
||||||
|
|
||||||
|
# Obtiene todos los archivos relacionados en artículos del sitio y los
|
||||||
|
# sube a Sutty de forma que los podamos seguir utilizando normalmente
|
||||||
|
# sin casos especiales (ej. soportar archivos locales al repositorio y
|
||||||
|
# remotos, alojados en Sutty)
|
||||||
|
#
|
||||||
|
# TODO: Convertir en reutilizable, por ejemplo correr en cada pull, no
|
||||||
|
# asumir que la migración se hizo una sola vez...
|
||||||
|
class StaticFileMigration
|
||||||
|
# Tipos de metadatos que contienen archivos
|
||||||
|
STATIC_TYPES = %w[file image].freeze
|
||||||
|
|
||||||
|
attr_reader :site
|
||||||
|
|
||||||
|
def initialize(site:)
|
||||||
|
@site = site
|
||||||
|
end
|
||||||
|
|
||||||
|
# Recorre todos los artículos cuyos layouts contengan campos con
|
||||||
|
# archivos estáticos
|
||||||
|
def migrate!
|
||||||
|
log = File.open(File.join(site.path, 'migration.log'), 'w')
|
||||||
|
modified = []
|
||||||
|
|
||||||
|
Dir.chdir site.path do
|
||||||
|
site.locales.each do |locale|
|
||||||
|
# Recorrer todos los documentos de todas las colecciones
|
||||||
|
site.posts(lang: locale).each do |doc|
|
||||||
|
# Ignoramos los documentos cuyo layout no contiene archivos
|
||||||
|
next unless layouts.include? doc.layout.name
|
||||||
|
|
||||||
|
remove = true
|
||||||
|
|
||||||
|
# Buscamos todos los campos con archivos
|
||||||
|
fields.each do |field|
|
||||||
|
next unless doc.attribute? field
|
||||||
|
next unless doc.document.data.key? field.to_s
|
||||||
|
|
||||||
|
# Traemos los metadatos, en este punto, Sutty cree que el
|
||||||
|
# archivo está subido, porque es una string apuntando a un
|
||||||
|
# archivo.
|
||||||
|
metadata = doc.public_send(field)
|
||||||
|
|
||||||
|
next if metadata.value['path'].blank?
|
||||||
|
|
||||||
|
path = Pathname.new(metadata.value['path'])
|
||||||
|
|
||||||
|
# Si no existe, agregamos una imagen faltante para no
|
||||||
|
# romper el sitio en Sutty
|
||||||
|
unless path.exist?
|
||||||
|
log.write "#{path} no existe\n"
|
||||||
|
path = Pathname.new(Rails.root.join('app/assets/images/logo.png'))
|
||||||
|
remove = false
|
||||||
|
end
|
||||||
|
|
||||||
|
# Agregamos el archivo al sitio y se lo asignamos al campo
|
||||||
|
metadata.value['path'] = {
|
||||||
|
io: path.open,
|
||||||
|
filename: path.basename
|
||||||
|
}
|
||||||
|
|
||||||
|
# Copiar y analizar el archivo sincrónicamente
|
||||||
|
metadata.static_file.blob.upload path.open
|
||||||
|
metadata.static_file.blob.analyze
|
||||||
|
|
||||||
|
next unless remove
|
||||||
|
|
||||||
|
dest = Pathname.new(metadata.send(:relative_destination_path))
|
||||||
|
|
||||||
|
# Eliminamos el archivo original y lo vinculamos al subido
|
||||||
|
# para mantener la ruta y no romper el sitio
|
||||||
|
FileUtils.rm_f path
|
||||||
|
# XXX: Link simbólico o duro?
|
||||||
|
FileUtils.ln_s dest.relative_path_from(path.dirname), path
|
||||||
|
end
|
||||||
|
|
||||||
|
# Guardamos los cambios
|
||||||
|
unless doc.save(false)
|
||||||
|
log.write "#{doc.path.relative} no se pudo guardar\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
modified << doc.path.absolute
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
log.close
|
||||||
|
|
||||||
|
# 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 = MigrationAuthor.new email: "sutty@#{Site.domain}",
|
||||||
|
name: 'Sutty'
|
||||||
|
end
|
||||||
|
|
||||||
|
# Encuentra todos los layouts con campos estáticos
|
||||||
|
def layouts
|
||||||
|
@layouts ||= site.layouts.reject do |_, layout|
|
||||||
|
layout.metadata.select do |_, desc|
|
||||||
|
STATIC_TYPES.include? desc['type']
|
||||||
|
end.empty?
|
||||||
|
end.keys
|
||||||
|
end
|
||||||
|
|
||||||
|
# Encuentra todos los campos con archivos estáticos
|
||||||
|
def fields
|
||||||
|
@fields ||= layouts.map do |layout|
|
||||||
|
site.layouts[layout].metadata.select do |_, desc|
|
||||||
|
STATIC_TYPES.include? desc['type']
|
||||||
|
end.keys
|
||||||
|
end.flatten.uniq.map(&:to_sym)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
7
config/initializers/analyze_job.rb
Normal file
7
config/initializers/analyze_job.rb
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# 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
|
|
@ -148,6 +148,7 @@ en:
|
||||||
anexo: 'Appendix'
|
anexo: 'Appendix'
|
||||||
simple: 'Simple'
|
simple: 'Simple'
|
||||||
sites:
|
sites:
|
||||||
|
static_file_migration: 'File migration'
|
||||||
index: 'This is the list of sites you can edit.'
|
index: 'This is the list of sites you can edit.'
|
||||||
edit_translations: "You can edit texts from your site other than
|
edit_translations: "You can edit texts from your site other than
|
||||||
posts', and you can also translate them to other languages."
|
posts', and you can also translate them to other languages."
|
||||||
|
|
|
@ -149,6 +149,7 @@ es:
|
||||||
anexo: 'Anexo'
|
anexo: 'Anexo'
|
||||||
simple: 'Simple'
|
simple: 'Simple'
|
||||||
sites:
|
sites:
|
||||||
|
static_file_migration: 'Migración de archivos'
|
||||||
index: 'Este es el listado de sitios que puedes editar.'
|
index: 'Este es el listado de sitios que puedes editar.'
|
||||||
edit_translations: 'Puedes editar los textos que salen en tu sitio
|
edit_translations: 'Puedes editar los textos que salen en tu sitio
|
||||||
que no corresponden a artículos aquí, además de traducirlos a
|
que no corresponden a artículos aquí, además de traducirlos a
|
||||||
|
|
30
doc/static_files.md
Normal file
30
doc/static_files.md
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# Archivos estáticos
|
||||||
|
|
||||||
|
El objetivo es encontrar una forma de importar los archivos estáticos de
|
||||||
|
un sitio que se ha migrado a la estructura de Sutty, es decir, subirlos
|
||||||
|
como archivos de Rails y luego vincularlos a los artículos.
|
||||||
|
|
||||||
|
Actualmente, los archivos primero se suben a la interfaz via
|
||||||
|
ActiveStorage y luego se vinculan internamente al repositorio[^git].
|
||||||
|
|
||||||
|
Lo que deberíamos lograr es reconocer los archivos vinculados desde los
|
||||||
|
artículos y hacer el proceso de cargarlos internamente, modificando los
|
||||||
|
artículos para que apunten a las nuevas versiones.
|
||||||
|
|
||||||
|
Esto podría hacerse por cada artículo individual o durante el proceso de
|
||||||
|
creación del sitio, después de clonarlo.
|
||||||
|
|
||||||
|
El algoritmo sería:
|
||||||
|
|
||||||
|
* Clonar el sitio
|
||||||
|
* Obtener todos los artículos de todas las colecciones
|
||||||
|
* Encontrar todos los layouts con archivos adjuntos
|
||||||
|
* Buscar todos los artículos que tengan esos layouts
|
||||||
|
* Tomar el archivo físico desde los metadatos y asociarlo al sitio via Active Storage
|
||||||
|
* Asociar el archivo al MetadataFile/Image correspondiente
|
||||||
|
* ???
|
||||||
|
* Migración cumplida!
|
||||||
|
|
||||||
|
|
||||||
|
[^git]: esto puede hacer que los repositorios git sean gigantes,
|
||||||
|
podríamos usar algo como git-annex quizás?
|
|
@ -63,6 +63,12 @@ class PostTest < ActiveSupport::TestCase
|
||||||
assert_not @post.valid?
|
assert_not @post.valid?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test 'se pueden guardar sin validar' do
|
||||||
|
assert @post.valid?
|
||||||
|
@post.title.value = ''
|
||||||
|
assert @post.save(false)
|
||||||
|
end
|
||||||
|
|
||||||
test 'se pueden guardar los cambios' do
|
test 'se pueden guardar los cambios' do
|
||||||
title = SecureRandom.hex
|
title = SecureRandom.hex
|
||||||
@post.title.value = title
|
@post.title.value = title
|
||||||
|
|
Loading…
Reference in a new issue