Volver a añadir las subidas faltantes a la base de datos

Tengo un sitio donde parece que se han eliminado varios archivos de la tabla de subidas en la base de datos, pero aún existen en el sistema de archivos. Esto deja los enlaces rotos. He solucionado algunos volviendo a subir los archivos a un nuevo tema para crear el registro en la BD (en ese momento pensé que los archivos también faltaban, pero @angus amablemente me señaló por qué no los encontraba: ¡algún día preguntaré: por qué tenemos tanto nombres sha1 como base62 para todos estos activos?) y luego regenerando los posts que incluyen esas subidas, así:

def rebake_posts_with_uploads(topic_id, post_number)
  p = Post.find_by(topic_id: topic_id, post_number: post_number)
  exit unless p
  re = /upload:\/\/(.+?)\)/
  shas= p.raw.scan(re)

  shas.each do |sha|
    posts = Post.where("raw like '%#{sha[0]}%'")
    next unless posts
    puts "Found #{posts.count - 1} #{sha[0]}"
    posts.each do |post|
      next if post.id == p.id
      puts "rebake #{post.id}--#{BASE_URL}/t/-/#{post.topic_id}/#{post.post_number}"
      post.rebake!
    end
  end
end

Mi nuevo plan, dado que parece que los archivos existen en el sistema de archivos pero no en la base de datos, es hacer algo así:

for (all files in /shared/uploads/default/original/1x) do |file|
  unless file is in uploads table
     create upload record 
     for each post that includes that upload record
       rebake

¿Eso parece correcto? Estoy revisando uploads.rake y no veo nada que parezca hacer esto ya. Esto es más o menos lo contrario de

pero en lugar de FileUtils.rm(file_path) haría un Upload.create, creo.

Si esto parece realmente tonto o hay una solución mucho mejor, me encantaría escucharla antes de meterme en este pequeño laberinto.

Gracias.

No sé cómo ocurrió esto. Esperaba poder culpar a un plugin personalizado, pero temo que no es el caso. Podría estar relacionado con otra discusión, en la que alguien dijo:

3 Me gusta

Sí, nuestra historia aquí con las subidas es bastante irregular, y es nuestra culpa.. @sam, ¿puedes recomendar a alguien que dé un consejo rápido?

2 Me gusta

Esto puede funcionar, solo tienes que tener cuidado de probar en local… También revisa los directorios 2x / 3x; hay subidas por todas partes.

3 Me gusta

Estoy intentando algo como esto:

def add_missing_files_to_uploads
  public_directory = Rails.root.join("public").to_s
  db = RailsMultisite::ConnectionManagement.current_db
  uploads_directory = File.join(public_directory, 'uploads', db).to_s
  # archivos de subida e imágenes optimizadas
  missing = 0
  matched = 0
  Dir.glob("#{uploads_directory}/**/*.pdf").each do |file_path|
    sha1 = Upload.generate_digest(file_path)
    url = file_path.split(public_directory, 2)[1]
    if (Upload.where(sha1: sha1).empty? &&
        Upload.where(url: url).empty?)
      puts "FALTA #{file_path}" si DEBUG
      missing += 1
    else
      matched += 1
    end
  end
  puts "FALTA: #{missing}. Coincidencia #{matched}"
end

¿Se parece más o menos? Mi primera prueba parece haber fallado, pero puede que haya cometido algún error.

Hola @pfaffman. Estaba leyendo este tema, ya que parece una solución prometedora para mi problema aquí. ¿Lograste obtener un buen resultado al final?

Lo siento, no puedo recordarlo.

A Sam y a mí nos parece que funcionará.

Acabo de resolver un problema con el simulador creando una publicación e insertando en ella un enlace a todas las imágenes, y luego reprocesando todas las publicaciones que contienen transparent.gif.

Esto parece una solución algo más elegante.

Diría que hagas una copia de seguridad solo de la base de datos y lo pruebes. Estoy un poco de vacaciones, pero si tienes presupuesto, puedo ver qué puedo hacer.

Yo también tengo un cliente importante en ams3; aún no los he migrado a AWS, pero creo que eso ocurrirá pronto. Tengo un amigo que trabajó para Digital Ocean y me recomendó ams3 porque era su mejor centro de datos y estaba en gran parte subutilizado (esto fue hace mucho tiempo). Eso no salió como esperaba.

1 me gusta

Me pregunto si este es el mejor método hoy en día.

Estoy tratando de recuperar subidas que están en el sistema de archivos pero no en la base de datos después de una mala migración a S3 (con el antiguo rake:s3_migrate).

En realidad no hay un buen método. :crying_cat:

Pero, sí. Esa sigue siendo la idea. No creo que nada haya cambiado en Discourse desde entonces. Normalmente, haría un par a mano para asegurarme de que hace lo esperado. Además, haz una copia de seguridad primero, y quizás pon el sitio en modo de solo lectura para que si necesitas restaurar la copia de seguridad no tengas que descartar ninguna publicación hecha mientras estabas jugando con las cosas.

Sin saber bastante más de lo que es fácil comunicar en un foro, no puedo decir si esa es realmente la mejor manera o si hay algo más sencillo. Podrías simplemente usar gsub en las rutas de las URL, por ejemplo. Si tienes presupuesto, puedes contactarme o preguntar en Marketplace.

1 me gusta

De acuerdo, lo resolví con Claude y muchos elogios. Comparto lo que hice para ayudar a cualquiera que tenga un problema similar o el mismo.

No estoy seguro de si este es el método más ingenioso y óptimo, solo el que funcionó para mí.

Por favor, tenga cuidado y tenga en cuenta que no soy un experto, sino un novato que siempre está aprendiendo.

El problema (S3 → sistema de archivos local)

Después de migrar de AWS S3 a un sistema de archivos local, muchas imágenes se mostraban como transparent.png. Los archivos siempre estaban en disco, pero Discourse no podía resolverlos.

La causa raíz fue una cadena rota:

  1. Publicaciones con URL cortas upload:// (SHA1 codificado en base62).
  2. Base de datos uploads mapeando SHA1 → ruta de archivo local.
  3. Sistema de archivos almacenando archivos nombrados por su hash SHA1,

La migración movió los archivos al disco correctamente, pero no existían registros de DB uploads. Sin un registro coincidente, Discourse recurre a transparent.png.

La solución (crear registros y volver a hornear)

Crear registros de carga faltantes a partir de archivos huérfanos:

dir = Rails.root.join("public", "uploads", "default", "original")
created = 0

Dir.glob(dir.join("**", "*")).select { |f| File.file?(f) }.each do |path|
  sha = File.basename(path, File.extname(path))
  next if Upload.find_by(sha1: sha)

  ext = File.extname(path).delete(".")
  relative = path.sub("#{Rails.root}/public", "")

  u = Upload.new
  u.sha1 = sha
  u.url = relative
  u.original_filename = File.basename(path)
  u.filesize = File.size(path)
  u.extension = ext
  u.user_id = -1
  u.save!(validate: false)

  created += 1
  puts "Created upload #{u.id}: #{sha}"
end

puts "Total created: #{created}"

Volver a hornear las publicaciones que hacen referencia a las cargas restauradas:

fixed_posts = 0

Upload.where(user_id: -1).find_each do |u|
  short = u.short_url
  next unless short

  Post.where("raw LIKE '%upload://%'").find_each do |p|
    urls = p.raw.scan(/upload:\/\/+[^\\s\\]\\)]+/)
    urls.each do |url|
      decoded = Upload.sha1_from_short_url(url)
      if decoded == u.sha1
        p.rebake!
        fixed_posts += 1
        puts "Rebaked post #{p.id}"
        break
      end
    end
  end
end

puts "Total rebaked: #{fixed_posts}"

Regenerar los optimizados faltantes:

Después de arreglar los archivos originales, necesitamos poblar los archivos optimizados (1X, 2X, etc.).

rake uploads:regenerate_missing_optimized

Reversión segura (por si acaso)

Todos los registros creados usan user_id: -1. Para deshacer:

Upload.where(user_id: -1).delete_all

delete_all omite los callbacks por lo que los archivos del sistema de archivos no se tocan.

Anteriormente usé destroy_all por error y activó callbacks que movieron archivos a la papelera.

Recuperé uno individual que usé para probar y reformulé mi enfoque.

3 Me gusta