Удаление загрузок через API

Я попытался получить к нему доступ, но, похоже, что API-эндпоинт для удаления загрузки не существует.

Muppet Show: Kermit's Trapeze Fall

Публикую это здесь, так как не смог найти никаких упоминаний или предложений по этой теме. Ближайшее, что я нашёл: API: a scope with access to /uploads

В настройках детализированных прав доступа ключа API для загрузок я вижу, что доступна опция «создание», но опция «удаление» отсутствует.

Похоже, что потенциальным обходным решением может быть создание пользовательской автоматизации и её запуск через API.

4 лайка

Какой у вас сценарий использования?

В зависимости от вашей конфигурации, загрузка файлов является общим ресурсом (ссылки на него содержатся в постах, профилях пользователей, значках и т. д.), поэтому удалять их без должной проверки довольно рискованно.

Кроме того, если файлы нигде не используются, они автоматически удаляются через некоторое время.

3 лайка

Сценарий использования заключается в своевременном удалении загрузок в рамках автоматизированных рабочих процессов с участием человека (human-in-the-loop) с помощью activepieces, data explorer и API.

Цель — предоставить обычным модераторам простой и надёжный способ немедленного полного удаления загрузок без доступа по SSH, а также комплексно охватить все известные случаи разрыва ссылок на загрузки и автоматически очистить соответствующие URL в CDN (в случае проксируемых аватаров — префикс URL, основанный на имени пользователя).


Меня успокоило то, что при тестировании выяснилось: ссылки на аватары, фоны профиля и фоны карточек автоматически обрабатываются при удалении загрузки.

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

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


Два основных сценария: «выжженная земля» и «хирургическое удаление». Вот текущая работа над сценарием «выжженная земля»:


Преобразование в список хешей загрузок:

  • URL аватаров (извлечь имя пользователя с помощью регулярного выражения) → получить хеш аватара через запрос в data explorer (хеш не содержится в URL)

  • URL тем/сообщений → собрать все хеши загрузок, использованные в этом сообщении, с помощью data explorer

  • Прямые URL оригинальных/оптимизированных загрузок (включая фоны профиля и карточек) → извлечь хеши с помощью регулярного выражения


Затем выполнить запрос по каждому хешу для поиска всех вхождений (один запрос data explorer охватывает все случаи, по одному хешу за раз):

  • список имён пользователей/ID тех, кто использовал этот хеш для аватара

  • список имён пользователей/ID тех, кто использовал этот хеш для фона профиля

  • список имён пользователей/ID тех, кто использовал этот хеш для фона карточки

  • список всех сообщений (в сыром виде), использующих эту загрузку


Действия:

  • приостановить всех пользователей, использовавших загрузку в качестве аватара, фона профиля или фона карточки

  • приостановить всех пользователей, у которых есть сообщения, ссылающиеся на целевую загрузку, но исключить случаи, когда ссылка находится внутри цитаты

  • удалить все темы

  • удалить все сообщения

  • уничтожить загрузку (отсутствует конечная точка API)

  • очистить все URL CDN (оптимизированные и неоптимизированные)

  • очистить стандартный префикс для проксируемых URL аватаров для каждого связанного имени пользователя (чтобы охватить все размеры)


Сценарий «хирургического удаления» по сути аналогичен, но не предусматривает приостановку связанных пользователей и требует некоторых изменений в части сбора всех хешей загрузок из URL сообщений/тем.

Вероятно, всё ещё потребуется удалять сами сообщения и темы, чтобы избежать битых ссылок, но если возможно, предпочтительнее было бы просто удалить разметку Markdown для конкретной загрузки (не затрагивая другие загрузки). Как в этой автоматизации, если бы она не удаляла все ссылки на загрузку в разметке.


В идеале я хотел бы иметь возможность блокировать хеши, чтобы после выполнения описанных выше действий кто-то не мог просто создать новый аккаунт и повторно загрузить тот же файл. :melting_face: :coffin:

На данный момент это, по-видимому, невозможно для обычного модератора, например, с помощью отслеживаемых слов. Поэтому, возможно, периодическое сканирование, аналогичное описанному выше, для списка хешей, станет способом решения этой проблемы.

1 лайк

Вы уже посмотрели наш Legal Compliance Plugin?

В нём есть эндпоинты для получения загрузок к посту и удаления загрузки по идентификаторам загрузки и поста.

Также он расширяет функционал поиска, позволяя находить все посты, содержащие определённую загрузку, по её хешу.

3 лайка

Я просто делюсь этой ссылкой для пользователей, которые прочитают и согласятся с вашим постом — возможно, им будет интересно проголосовать за это (знаю, что вы уже видели это):

2 лайка

Да! Я чуть не расплакался от радости, увидев ваш плагин в начале этого месяца, ха-ха. Огромное спасибо за его создание и публикацию. :heart:

Однако есть сценарии, которые мне не удалось охватить с его помощью:

  • С использованием префикса поиска upload: не удалось найти фоны профилей или фоны карточек (если только кто-то не вставил прямую ссылку на эти изображения в пост).
  • Безвозвратное удаление изображений в таких случаях (изображение, не связанное ни с одним постом).
  • Аналогично для аватаров: получить хеш аватара (обычно он не указан в URL), затем найти все другие аккаунты, которые, возможно, использовали его, если требуется блокировка, и удалить загрузку, не связанную с постом.

Желательные функции:

  • Запуск вебхука при удалении для очистки CDN всех вариантов загрузок после их удаления.
  • Массовое/автоматическое удаление всех постов и тем, содержащих ссылку на загрузку, при удалении самой загрузки.

В веб-интерфейсе Discourse, похоже, невозможно удалить (отвязать) аватар пользователя — ни администратору/модератору, ни самому пользователю (кроме как загрузив замену). Это возможно для фона профиля и баннера. Однако в обоих случаях загрузка не уничтожается сразу, и если существует другой неизвестный пользователь, использующий ту же загрузку для части своего профиля (аватар, фон профиля, карточка профиля), она также не будет удалена позже.


В итоге я решил, что стоит попробовать создать автоматизированные рабочие процессы, способные обрабатывать большую часть или весь процесс (с человеческим контролем/одобрением и/или упрощённым вводом данных). Это позволит применять решение последовательно, охватывать крайние случаи и, в идеале, минимизировать вероятность наличия постов с неработающими ссылками на удалённые загрузки. Также необходимо автоматически блокировать пользователей при необходимости (если целевая загрузка не находится внутри цитаты) и очищать CDN.


Вот что у меня пока есть для запросов в Data Explorer (ещё не на этапе «связывания» их вместе) с целью охвата:

  • URL тем
  • URL постов
  • Прямые URL изображений (посты, фон профиля, фон карточки)
  • URL аватаров (проксируемые)

Подготовка списка хешей загрузок

username (из проксируемого URL аватара) к хешу загрузки

-- [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 поста/темы к списку хешей загрузок

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

Другие типы URL загрузок, о которых мне известно, содержат хеш непосредственно в URL (для получения этих хешей не требуется использовать Data Explorer).


Сбор и подготовка всей доступной информации о хеше загрузки

Пример параметров:

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

cdn_url_prefix:
https://cdn.example.com

app_hostname:
www.example.com

upload_hash:
0000000000000000000000000000000000000000

Поиск по хешу и сводка

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

Не охватывает: темы/посты в очереди на проверку или черновики постов.

Вывод:

upload_ids (должен быть только один)
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

Это предоставит необходимую информацию для выборочного использования следующих API:

А также для дальнейших действий:

  • Очистка всех URL CDN (все варианты: оптимизированные и оригинальные и т.д.)
  • Очистка URL с префиксом проксируемых аватаров (для покрытия всех размеров)

Автоматически обрабатывается Discourse:

  • Удаление ссылки на загрузку из всех профилей пользователей (аватар, фон профиля и фон карточки) немедленно при уничтожении/удалении загрузки.
1 лайк