Re-adicionando uploads ausentes ao banco de dados

Tenho um site onde parece que vários uploads foram removidos da tabela uploads no banco de dados, mas ainda existem no sistema de arquivos. Isso deixa os links para esses arquivos quebrados. Corrigi alguns reenviando os arquivos para um novo tópico, criando assim o registro no banco de dados (na época eu achava que os arquivos também estavam faltando, mas @angus gentilmente apontou por que eu não os encontrava — Um dia vou perguntar: por que temos tanto nomes sha1 quanto base62 para todos esses ativos?) e depois reprocessando os posts que incluem esses uploads, assim:

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

Meu novo plano, já que parece que os arquivos existem no sistema de arquivos, mas não no banco de dados, é fazer algo assim:

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

Isso parece correto? Estou analisando uploads.rake e não vejo nada que faça isso já. Isso é meio que o oposto de

mas, em vez de FileUtils.rm(file_path), eu faria um Upload.create, acredito.

Se isso parecer muito absurdo ou se houver uma solução muito melhor, adoraria ouvir antes de entrar nessa pequena toca de coelho.

Obrigado.

Não sei como isso aconteceu. Eu esperava culpar um plugin personalizado, mas temo que não seja o caso. Pode estar relacionado a outra discussão, em que alguém disse:

Sim, nossa história aqui com uploads é bastante irregular, e a culpa é nossa.. @sam, você pode recomendar alguém para dar uma breve orientação?

Isso pode funcionar, só é preciso ter cuidado para testar localmente… também verifique os diretórios 2x / 3x, há uploads em todos os lugares.

Estou tentando algo assim:

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 e imagens otimizadas
  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

Isso parece meio próximo? Meu primeiro teste parece ter falhado, mas pode ser que eu tenha errado alguma coisa.

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.

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.

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.