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
|
||||
def static_file
|
||||
return @static_file if @static_file
|
||||
|
||||
ActiveRecord::Base.connection_pool.with_connection do
|
||||
if uploaded?
|
||||
blob = ActiveStorage::Blob.find_by(key: key_from_path)
|
||||
|
@ -68,10 +70,12 @@ class MetadataFile < MetadataTemplate
|
|||
|
||||
private
|
||||
|
||||
def path
|
||||
@path ||= Pathname.new value['path']
|
||||
end
|
||||
|
||||
def key_from_path
|
||||
# XXX: No podemos usar self#extension porque en este punto todavía
|
||||
# no sabemos el static_file
|
||||
File.basename(value['path'], '.*')
|
||||
path.dirname.basename.to_s
|
||||
end
|
||||
|
||||
# Hacemos un link duro para colocar el archivo dentro del repositorio
|
||||
|
@ -83,7 +87,7 @@ class MetadataFile < MetadataTemplate
|
|||
end
|
||||
|
||||
def extension
|
||||
static_file.blob.content_type.split('/').last
|
||||
@extension ||= static_file.filename.to_s.split('.').last
|
||||
end
|
||||
|
||||
# Obtener la ruta al archivo
|
||||
|
@ -97,7 +101,7 @@ class MetadataFile < MetadataTemplate
|
|||
end
|
||||
|
||||
def relative_destination_path
|
||||
File.join('public', [static_file.key, extension].join('.'))
|
||||
File.join('public', static_file.key, static_file.filename.to_s)
|
||||
end
|
||||
|
||||
def destination_path
|
||||
|
|
|
@ -156,8 +156,8 @@ class Post < OpenStruct
|
|||
|
||||
# Guarda los cambios
|
||||
# rubocop:disable Metrics/CyclomaticComplexity
|
||||
def save
|
||||
return false unless valid?
|
||||
def save(validation = true)
|
||||
return false if validation && !valid?
|
||||
# Salir si tenemos que cambiar el nombre del archivo y no pudimos
|
||||
return false if !new? && path_changed? && !update_path!
|
||||
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
|
||||
# de crearlo
|
||||
after_initialize :load_jekyll
|
||||
after_create :load_jekyll
|
||||
after_create :load_jekyll, :static_file_migration!
|
||||
# Cambiar el nombre del directorio
|
||||
before_update :update_name!
|
||||
# Guardar la configuración si hubo cambios
|
||||
|
@ -323,6 +323,11 @@ class Site < ApplicationRecord
|
|||
config.url = url
|
||||
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
|
||||
#
|
||||
|
|
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'
|
||||
simple: 'Simple'
|
||||
sites:
|
||||
static_file_migration: 'File migration'
|
||||
index: 'This is the list of sites you can edit.'
|
||||
edit_translations: "You can edit texts from your site other than
|
||||
posts', and you can also translate them to other languages."
|
||||
|
|
|
@ -149,6 +149,7 @@ es:
|
|||
anexo: 'Anexo'
|
||||
simple: 'Simple'
|
||||
sites:
|
||||
static_file_migration: 'Migración de archivos'
|
||||
index: 'Este es el listado de sitios que puedes editar.'
|
||||
edit_translations: 'Puedes editar los textos que salen en tu sitio
|
||||
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?
|
||||
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
|
||||
title = SecureRandom.hex
|
||||
@post.title.value = title
|
||||
|
|
Loading…
Reference in a new issue