API経由でのアップロード削除

それを探してみましたが、アップロードを削除するためのAPIエンドポイントは存在しないようです。

Muppet Show: Kermit's Trapeze Fall

これに関する言及や提案が見つからなかったため、ここに投稿します。最も近いものとして見つけたのは、API: a scope with access to /uploads です。

アップロードのAPIキーの粒度のスコープ設定では、「作成」は利用可能ですが、「削除」は利用できないことがわかります。

潜在的な回避策としては、カスタムオートメーションを作成し、それをAPI経由でトリガーすることが考えられます。

「いいね!」 4

ユースケースは何ですか?

設定によっては、アップロードは共有リソース(投稿、ユーザープロフィール、バッジなどによって参照される)であるため、十分な注意を払わずに削除するのはいくらか危険です。

さらに、どこからも参照されていないアップロードは、しばらくすると自動的にクリーンアップされます。

「いいね!」 3

このユースケースは、activepiecesデータエクスプローラー、およびAPIを使用して、ヒューマン・イン・ザ・ループの自動化ワークフローの一部として、時間的制約のあるアップロードの削除を行うためのものです。

目標は、通常のモデレーターが、SSHアクセスなしで、アップロード参照を壊す既知のすべてのケースを包括的にカバーし、特定のURL(およびプロキシされたアバターの場合はユーザー名に基づくURLプレフィックス)を自動的にCDNパージできるように、自信を持って即座にアップロードを完全に削除するための簡単な方法を利用できるようにすることです。


アップロードが破棄されたときに、アバター、プロフィール背景、カード背景の参照が自動的に処理されるのを見て、テストで安心しました。

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

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


主なシナリオは「焦土作戦」と「外科的削除」の2つになります。これが焦土作戦の作業中の内容です。


アップロードハッシュリストへの変換:

  • アバターURL(正規表現を使用してユーザー名を抽出)→データエクスプローラークエリでアバターハッシュを取得(ハッシュはURLに含まれていません)

  • トピック/投稿URL → データエクスプローラーを使用して、その投稿で使用されているすべてのアプロードハッシュを収集

  • 直接のオリジナル/最適化されたアップロードURL(プロフィール背景とカード背景を含む)→正規表現を使用してハッシュを抽出


次に、各ハッシュをクエリして、すべての出現箇所を見つけます(一度に1つのハッシュに対して、すべてのケースをカバーする1つのデータエクスプローラークエリ):

  • アバターに使用したユーザーのユーザー名/IDリスト

  • プロフィール背景に使用したユーザーのユーザー名/IDリスト

  • カード背景に使用したユーザーのユーザー名/IDリスト

  • このアップロードを使用しているすべての投稿(生)のリスト


アクション:

  • アバター、プロフィール背景、またはカード背景にアップロードを使用したすべてのユーザーを一時停止

  • ターゲットのアップロードを参照する投稿を持つすべてのユーザーを一時停止しますが、その参照が引用符の内側にある場合は除外します

  • すべてのトピックを削除

  • すべての投稿を削除

  • アップロードを破棄(APIエンドポイントなし)

  • すべてのCDN URL(最適化済み/未最適化)をパージ

  • 関連付けられた各ユーザー名について、プロキシされたアバターURLの標準プレフィックスをパージします(すべてのサイズをカバーするため)


外科的削除のシナリオは基本的に同じですが、関連するユーザーを一時停止せず、投稿/トピックURLからすべてのアプロードハッシュを収集する方法に関していくつかの変更が必要です。

壊れた参照を避けるために、投稿/トピック自体を削除するのが最善かもしれませんが、その特定のアップロードのアップロードマークダウンを削除すること(他のアップロードには触れない)は、可能であればより優れています。これは、すべてのマークダウンアップロード参照を削除しない場合のこの自動化のようです。


理想的には、ハッシュをブロックできるようにしたいです。そうすれば、上記の手順を実行した後、誰かが単に新しいアカウントを作成して再アップロードすることを防ぐことができます。:melting_face: :coffin:

これは、現在のところ、監視対象の単語を使用するなどして、通常のモデレーターが行えることではないと思います。したがって、上記のような定期的なスキャンを実行してハッシュのリストを処理する方法が、それを処理する1つの方法かもしれません。

「いいね!」 1

Legal Compliance Plugin を確認しましたか?

投稿のアップロードを取得したり、アップロードと投稿のIDを指定してアップロードを削除したりするためのエンドポイントがあります。

また、ハッシュを指定して特定のアップロードを含むすべての投稿を取得するように検索機能も拡張されています。

「いいね!」 2

あなたの投稿に同意する読者のために、こちらにリンクしておきます。彼らは投票に興味を持つかもしれません(あなたはすでに読んでいると思いますが):

「いいね!」 2

はい!先月あなたのプラグインを見たとき、思わず感涙しました(笑)。作成して共有していただき、本当にありがとうございます。 :heart:

カバーできなかったシナリオがいくつかあります。

  • アップロードの使用: 検索プレフィックスでは、プロフィール背景やカード背景を見つけることはできないようでした(誰かがその画像を投稿に直接リンクしていない限り)。
  • そのシナリオでの画像のハード削除(投稿との関連付けがない画像)。
  • アバターについても同様です。例えば、アバターのハッシュを取得し(通常はURLに含まれていません)、その後、サスペンドが必要な場合に同じアバターを使用した可能性のある他のアカウントを見つけ、投稿に関連付けのないアップロードを削除する、など。

あると便利な機能:

  • 削除時にWebhookをトリガーして、アップロードのすべてのバリアントの削除後にCDNパージをトリガーする。
  • アップロードが削除されたときに、アップロード参照を持つすべての投稿/トピックを一括/自動削除する。

Discourse(Web UI)では、管理者/モデレーターまたはユーザー自身がユーザーのアバターを削除(リンク解除)することはできないようです(代替をアップロードする以外)。プロフィール背景とバナーについては可能です。しかし、これらはいずれもアップロードを即座に破棄せず、もし別の未知のユーザーが自分のプロファイルの一部(アバター、プロフィール背景、プロフィールカード)に同じアップロードを使用している場合、後でパージされることもありません。


ほとんどすべて、またはすべてのプロセスを処理できる自動化されたワークフローを作成することが価値があるかもしれないと考えました(人間のレビュー/承認や/または簡略化された人間の入力を伴います)。一貫して適用しやすく、エッジケースをカバーし、理想的には削除されたアップロード参照を持つ未削除の投稿の可能性を最小限に抑えるためです。必要に応じて自動サスペンド(アップロードが引用符内にある場合を除く)とCDNパージも行います。


これは、データエクスプローラーのクエリで、まだ「それらをまとめる」段階には達していませんが、カバーしたい目標は次のとおりです。

  • トピックURL
  • 投稿URL
  • 画像の直接URL(投稿、プロフィール背景、カード背景)
  • アバターURL(プロキシ経由)

アップロードハッシュリストの準備

ユーザー名(プロキシされたアバター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自体にハッシュが含まれているため(これらのハッシュを取得するためにデータエクスプローラーを使用する必要はありません)、ハッシュが含まれています。


アップロードハッシュに関するすべての実行可能な情報の収集と準備

パラメータの例:

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 (1つのみであるはず)
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を使用するために必要な情報が提供されます:

さらに実行するアクション:

  • すべてのCDN URLをパージする(すべてのバリアント、最適化されたもの、オリジナルなど)
  • プロキシされたアバターのプレフィックスURLをパージする(すべてのサイズをカバーするため)

Discourseによって自動的に処理されること:

  • アップロードが破棄/削除されたときに、すべてのユーザープロファイル(アバター、プロフィール背景、カード背景)からアップロード参照を即座に削除する。
「いいね!」 1