Eu tentei, mas parece que um endpoint de API para excluir um upload não existe.
Estou postando isso aqui, pois não consegui encontrar menções ou sugestões sobre isso, o mais próximo que encontrei foi: API: a scope with access to /uploads
Nas configurações de escopo granular da chave de API para uploads, vejo que ‘create’ (criar) está disponível, mas ‘delete’ (excluir) não está.
Parece que uma possível solução alternativa poderia ser criar uma automação personalizada e acioná-la via API.
Dependendo da sua configuração, uploads sendo um recurso compartilhado (referenciado por posts, perfil de usuário, emblemas, etc…) é um tanto arriscado excluí-los sem a devida diligência.
Além disso, eles são limpos automaticamente após um curto período se não estiverem sendo referenciados em nenhum lugar.
O caso de uso é para remoção de uploads com tempo crítico como parte de fluxos de trabalho de automação com intervenção humana usando activepieces, data explorer e a API.
O objetivo é permitir que moderadores regulares tenham acesso a uma maneira simples de remover completamente e com confiança os uploads imediatamente, sem acesso SSH, e cobrir de forma abrangente todos os casos conhecidos de quebra de referências de upload, além de limpar automaticamente o CDN dos URLs específicos (e no caso de avatares proxied, o prefixo do URL baseado no nome de usuário).
Fiquei aliviado ao ver em testes que as referências de avatar, plano de fundo do perfil e plano de fundo do cartão são tratadas automaticamente quando um upload é destruído.
Os dois cenários principais seriam ‘terra arrasada’ e ‘remoção cirúrgica’. Este é o trabalho em andamento para terra arrasada:
Conversão para uma lista de hashes de upload:
URLs de avatar (extrair nome de usuário usando regex) → obter hash de avatar via consulta do data explorer (o hash não está no URL)
URLs de tópico/postagem → coletar todos os hashes de upload usados nessa postagem usando data explorer
URLs de upload originais/otimizados diretos (incluindo plano de fundo do perfil e plano de fundo do cartão) → extrair hashes usando regex
Em seguida, consultar cada hash para encontrar todas as ocorrências (uma consulta de data explorer para cobrir todos os casos, um hash por vez):
lista de nome de usuário/ID, para usuários que o usaram como avatar
lista de nome de usuário/ID, para usuários que o usaram como plano de fundo do perfil
lista de nome de usuário/ID, para usuários que o usaram como plano de fundo do cartão
lista de todas as postagens (raw) que usam este upload
Ações:
suspender todos os usuários que usaram o upload como avatar, plano de fundo do perfil ou plano de fundo do cartão
suspender todos os usuários que têm postagens referenciando o upload alvo, mas excluir se a referência estiver aninhada dentro de uma citação
excluir todos os tópicos
excluir todas as postagens
destruir upload (sem endpoint de API)
limpar todos os URLs do CDN (otimizados/não otimizados)
limpar o prefixo padrão para URLs de avatar proxied para cada nome de usuário associado (para cobrir todos os tamanhos)
O cenário de remoção cirúrgica seria essencialmente o mesmo, mas sem suspender os usuários associados e precisaria de algumas alterações em relação à coleta de todos os hashes de upload de URLs de postagem/tópico.
Provavelmente ainda excluir as postagens/tópicos em si para evitar referências quebradas, mas remover o markdown do upload para aquele upload específico (sem tocar em outros uploads) seria superior, se possível. Como esta automação, se não removesse todas as referências de upload do markdown.
Idealmente, eu gostaria de poder bloquear hashes também, então, depois de passar pelo acima, alguém não poderia simplesmente criar uma nova conta e fazer upload novamente.
Eu não acho que seja possível atualmente para um moderador regular, como usando palavras vigiadas. Então, talvez fazer uma varredura periódica como a acima para uma lista de hashes seria uma maneira de lidar com isso.
Sim! Eu praticamente chorei lágrimas de alegria ao ver seu plugin no início deste mês, haha. Um enorme obrigado por criá-lo e compartilhá-lo.
Alguns cenários que não consegui cobrir com ele:
Usando o upload: prefixo de pesquisa, não parecia possível encontrar planos de fundo de perfil ou planos de fundo de cartão (a menos que alguém tivesse linkado diretamente essas imagens em uma postagem também).
Excluir permanentemente imagens nesse cenário (imagem sem associação com postagens).
Semelhante para Avatar, por exemplo, obter o hash do avatar (geralmente não está no URL), então encontrar quaisquer outras contas que possivelmente também o usaram, se uma suspensão for necessária + excluir o upload sem associação de postagem.
Seria bom ter:
Acionar um webhook na exclusão, para acionar a purga de CDN após a exclusão para todas as variantes dos uploads.
Excluir em massa/automaticamente todas as postagens/tópicos que referenciam o upload quando o upload é excluído.
No Discourse (interface web), parece não ser possível excluir (desvincular) o avatar de um usuário, seja por um admin/mod ou pelo próprio usuário (além de enviar um substituto). É possível para o plano de fundo do perfil e o banner. Mas ambos não destroem o upload imediatamente e, se existir outro usuário desconhecido que use o mesmo upload para parte de seu perfil (avatar, plano de fundo do perfil, cartão de perfil), ele também não será purgado mais tarde.
Acabei pensando que poderia valer a pena tentar criar alguns fluxos de trabalho automatizados que possam lidar com a maior parte ou todo o processo (com revisão/aprovação humana e/ou entrada humana simplificada). Para facilitar a aplicação consistente, cobrir casos extremos e, idealmente, minimizar a possibilidade de ter postagens não excluídas que tenham referências de upload mortas. Também suspender automaticamente, se necessário (a menos que o upload de destino esteja dentro de uma citação) e purgar o CDN.
Isto é o que eu tinha até agora para as consultas do explorador de dados (ainda não na parte de ‘juntá-las’), com o objetivo de cobrir:
URLs de tópicos
URLs de postagens
URLs de imagens diretas (postagens, plano de fundo do perfil, plano de fundo do cartão)
URLs de avatar (proxied)
Preparar Lista de Hashes de Upload
nome de usuário (a partir do URL de avatar proxied) para hash de upload
-- [params]
-- user_id :username
SELECT
up.sha1 AS upload_hash
FROM
users u
JOIN uploads up ON up.id = u.uploaded_avatar_id
WHERE
u.id = :username
URL de postagem/tópico para lista de hashes de upload
-- [params]
-- post_id :post_url
SELECT
COALESCE(json_agg(up.sha1), '[]'::json) AS upload_hashes
FROM
upload_references ur
JOIN uploads up ON up.id = ur.upload_id
WHERE
ur.target_id = :post_url
AND ur.target_type = 'Post'
Os outros tipos de URL de upload de que estou ciente têm o hash no próprio URL (não é necessário usar o explorador de dados para obter esses hashes).
Coletar e preparar todas as informações acionáveis sobre um hash de upload
-- [params]
-- string :upload_hash
-- string :upload_schemeless_prefix
-- string :cdn_url_prefix
-- string :app_hostname
WITH target_uploads AS (
SELECT
id,
url,
user_id
FROM
uploads
WHERE
sha1 = :upload_hash
),
all_urls AS (
SELECT
url
FROM
target_uploads
UNION
SELECT
url
FROM
optimized_images
WHERE
upload_id IN (
SELECT
id
FROM
target_uploads)
),
post_data AS (
SELECT
p.id AS post_id,
p.topic_id,
u.id AS user_id,
u.username AS author,
p.created_at,
CASE WHEN p.post_number = 1 THEN
TRUE
ELSE
FALSE
END AS is_topic_starter,
CASE WHEN t.archetype = 'private_message' THEN
TRUE
ELSE
FALSE
END AS is_dm,
p.raw
FROM
upload_references ur
JOIN posts p ON p.id = ur.target_id
AND ur.target_type = 'Post'
JOIN topics t ON t.id = p.topic_id
JOIN users u ON u.id = p.user_id
WHERE
ur.upload_id IN (
SELECT
id
FROM
target_uploads)
AND p.deleted_at IS NULL
)
SELECT
(
SELECT
COALESCE(json_agg(id), '[]'::json)
FROM
target_uploads) AS upload_ids,
(
SELECT
COALESCE(json_agg(url), '[]'::json)
FROM
all_urls) AS upload_s3_schemeless_urls,
(
SELECT
COALESCE(json_agg(
CASE WHEN url LIKE '//%' THEN
REPLACE(url, :upload_schemeless_prefix, :cdn_url_prefix)
ELSE
:cdn_url_prefix || url
END), '[]'::json)
FROM
all_urls) AS upload_cdn_urls,
(
SELECT
COALESCE(json_agg(json_build_object('user_id', id, 'username', username)), '[]'::json)
FROM
users
WHERE
uploaded_avatar_id IN (
SELECT
id
FROM
target_uploads)) AS avatar_users,
(
SELECT
COALESCE(json_agg('https://' || :app_hostname || '/user_avatar/' || :app_hostname || '/' || username || '/'), '[]'::json)
FROM
users
WHERE
uploaded_avatar_id IN (
SELECT
id
FROM
target_uploads)) AS avatar_proxied_url_prefixes,
(
SELECT
COALESCE(json_agg(json_build_object('user_id', u.id, 'username', u.username)), '[]'::json)
FROM
user_profiles up
JOIN users u ON u.id = up.user_id
WHERE
up.profile_background_upload_id IN (
SELECT
id
FROM
target_uploads)) AS profile_background_users,
(
SELECT
COALESCE(json_agg(json_build_object('user_id', u.id, 'username', u.username)), '[]'::json)
FROM
user_profiles up
JOIN users u ON u.id = up.user_id
WHERE
up.card_background_upload_id IN (
SELECT
id
FROM
target_uploads)) AS card_background_users,
(
SELECT
COALESCE(json_agg(pd.topic_id), '[]'::json)
FROM
post_data pd
WHERE
pd.is_topic_starter = TRUE) AS topic_ids,
(
SELECT
COALESCE(json_agg(pd.post_id), '[]'::json)
FROM
post_data pd
WHERE
pd.is_topic_starter = FALSE) AS post_ids,
(
SELECT
COALESCE(json_agg(json_build_object('post_id', pd.post_id, 'topic_id', pd.topic_id, 'user_id', pd.user_id, 'author', pd.author, 'created_at', pd.created_at, 'is_topic_starter', pd.is_topic_starter, 'is_dm', pd.is_dm, 'raw', pd.raw)), '[]'::json)
FROM
post_data pd) AS post_details
Não cobre: tópicos na fila de revisão pendente ou rascunhos de postagens
Saídas:
upload_ids (deve ser apenas um)
upload_s3_schemeless_urls
upload_cdn_urls
avatar_users
avatar_proxied_url_prefixes
profile_background_users
card_background_users
topic_ids
post_ids
post_details
Isso fornecerá as informações necessárias para usar essas APIs seletivamente:
Suspender um usuário (para postagens, pode usar raw em post_details para excluir condicionalmente usuários se o markdown do upload estiver dentro de uma citação)
Exclusão de Upload (API não existe atualmente)
E para ações futuras:
Excluir todas as URLs de CDN (todas as variantes, otimizadas e originais, etc.)
Excluir prefixos de URLs de avatares proxied (para cobrir todos os tamanhos)
Tratado automaticamente pelo Discourse:
Remove a referência de upload de todos os perfis de usuário (avatar, plano de fundo do perfil e plano de fundo do cartão) imediatamente quando um upload é destruído/excluído.