Re-adicionando uploads ausentes ao banco de dados

I’ve got a site where it seems that a bunch of uploads have been removed from the uploads table in the database, but still exist in the filesystem. This leaves links to these broken. I’ve fixed a few by re-uploading files into a new topic to create the record in the DB (at the time I thought that the files were missing too, but @angus graciously pointed out why I wasn’t finding them–One day I’ll ask: why do we have both sha1 and base62 names for all of these assets?) and then re-baking the posts that include those uploads, like this:

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

My new plan, since it seems that the files exist in the file system but not in the database, is to do something like this:

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

Does that seem right? I’m looking at uploads.rake and don’t see anything that seems to do this already. This is sort of the opposite of

but instead of FileUtils.rm(file_path) I’d instead do an Upload.create, I think.

If this seems really stupid or there is a much better solution, I’d love to hear it before I go down this little rabbit hole.

Thanks.

I don’t know how this happened. I was hoping to pin the blame on a custom plugin, but I’m afraid that’s not the case. It may be related to another discussion, in which someone said:

3 curtidas

Yeah our history here with uploads is quite spotty, and it is our fault… @sam can you recommend someone to give a quick bit of advice?

2 curtidas

This can work, you just have to be careful to test on local … also look at 2x / 3x directories there are uploads everywhere.

3 curtidas

I’m trying something like this:

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
  # uploads and optimized images
  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 "MISSING #{file_path}" if DEBUG
      missing += 1
    else
      matched += 1
    end
  end
  puts "MISS: #{missing}. Match #{matched}"
end

Does that look sort of close? My first test seems to have failed, but I might have screwed something up.

Olá @pfaffman. Eu estava lendo este tópico, pois ele parece uma solução promissora para meu problema aqui.
Você conseguiu chegar a um bom resultado no final?

Desculpe, não consigo lembrar.

Para o Sam e para mim, parece que vai funcionar.

Acabei de resolver um problema no simulador criando um tópico e inserindo nele um link para todas as imagens, depois reprocessando todos os tópicos que contêm transparent.gif.

Isso parece ser uma solução um pouco mais elegante.

Eu diria para fazer um backup apenas do banco de dados e tentar. Estou meio de férias, mas se você tiver um orçamento, posso ver o que consigo fazer.

E eu também tenho um cliente importante em ams3; ainda não os migrei para a AWS, mas acho que isso vai acontecer em breve. Tenho um amigo que trabalhava na DigitalOcean e recomendou ams3 porque era o melhor data center deles e estava amplamente subutilizado (isso foi há muito tempo). Não deu certo como eu esperava.

1 curtida

Eu me pergunto se este é o melhor método hoje em dia?

Estou tentando recuperar uploads que estão no sistema de arquivos, mas não no banco de dados, após uma migração S3 ruim (com o antigo rake:s3_migrate).

Não há realmente um bom método. :crying_cat:

Mas, sim. Essa ainda é a ideia. Eu não acho que nada tenha mudado no Discourse desde então. Normalmente, eu faria alguns manualmente para garantir que ele faça o que é esperado. Além disso, faça um backup primeiro e talvez coloque o site em modo somente leitura para que, se você precisar restaurar o backup, não precise descartar nenhuma postagem feita enquanto você estava mexendo nas coisas.

Sem saber muito mais do que é fácil comunicar em um fórum, não posso dizer se essa é realmente a melhor maneira ou se há algo mais simples. Você pode ser capaz de apenas usar gsub nos caminhos dos URLs, por exemplo. Se você tiver um orçamento, pode entrar em contato comigo ou perguntar em Marketplace.

1 curtida

Ok, eu resolvi com o Claude e muitos elogios. Estou compartilhando o que fiz para ajudar qualquer outra pessoa com um problema semelhante ou o mesmo.

Não tenho certeza se este é o método mais inteligente e otimizado a ser usado, apenas o que funcionou para mim.

Por favor, tenha cuidado e tenha em mente que não sou um especialista, mas um novato sempre aprendendo.

O problema (S3 → sistema de arquivos local)

Após a migração do AWS S3 para o sistema de arquivos local, muitas imagens eram exibidas como transparent.png. Os arquivos estavam sempre no disco, mas o Discourse não conseguia resolvê-los.

A causa raiz era uma cadeia quebrada:

  1. Posts com URLs curtas upload:// (SHA1 codificado em base62).
  2. Banco de dados uploads mapeando SHA1 → caminho do arquivo local.
  3. Sistema de arquivos armazenando arquivos nomeados pelo seu hash SHA1,

A migração moveu os arquivos para o disco corretamente, mas não havia registros de DB uploads. Sem um registro correspondente, o Discourse recorre ao transparent.png.

A solução (criar registros e reprocessar)

Criar registros de upload ausentes a partir de arquivos órfãos:

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}"

Reprocessar posts que referenciam uploads restaurados:

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 otimizados ausentes:

Após corrigir os arquivos originais, precisamos preencher os arquivos otimizados (1X, 2X, etc.).

rake uploads:regenerate_missing_optimized

Rollback seguro (só por precaução)

Todos os registros criados usam user_id: -1. Para desfazer:

Upload.where(user_id: -1).delete_all

delete_all ignora os callbacks, então os arquivos do sistema de arquivos não são afetados.

Anteriormente, usei destroy_all por engano e isso acionou callbacks que moveram os arquivos para a lixeira.

Recuperei um individual que usei para testar e reformulei minha abordagem.

3 curtidas