Exclusão de Upload via API

Eu tentei, mas parece que um endpoint de API para excluir um upload não existe.

Muppet Show: Kermit's Trapeze Fall

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.

4 curtidas

Qual é o seu caso de uso?

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.

3 curtidas

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.

https://github.com/discourse/discourse/commit/e1975e293f2625259e925b4a3c93d88d5acfcaa8

https://github.com/discourse/discourse/commit/38e7b1a0492dd4282c3cd3b1ddb2b3343661d31f


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. :melting_face: :coffin:

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.

1 curtida

Você já conferiu nosso Legal Compliance Plugin?

Ele possui endpoints para obter os uploads de uma postagem e para remover um upload dados os IDs do upload e da postagem.

Ele também estende a funcionalidade de busca para obter todas as postagens que contêm um upload específico, dado o hash.

2 curtidas

Estou apenas linkando isto para os usuários que lerem e concordarem com sua postagem, eles podem se interessar em votar nisto (sei que você já leu):

2 curtidas

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. :heart:

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

parâmetros de exemplo:

upload_schemeless_prefix:
//my-s3-bucket.somehost.example.com

cdn_url_prefix:
https://cdn.example.com

app_hostname:
www.example.com

upload_hash:
0000000000000000000000000000000000000000

pesquisa de hash e resumo

-- [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:

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.
1 curtida