Upload-Löschung über API

Ich habe danach gegriffen, aber es scheint, dass ein API-Endpunkt zum Löschen eines Uploads nicht existiert.

Muppet Show: Kermit's Trapeze Fall

Ich poste dies hier, da ich keine Erwähnungen oder Vorschläge dazu finden konnte. Das Nächstliegende, was ich gefunden habe, war: API: a scope with access to /uploads

In den granularen Umfangseinstellungen des API-Schlüssels für Uploads sehe ich, dass „erstellen“ verfügbar ist, „löschen“ jedoch nicht.

Es scheint, dass eine mögliche Umgehung darin bestehen könnte, eine benutzerdefinierte Automatisierung zu erstellen und diese über die API auszulösen.

4 „Gefällt mir“

Was ist Ihr Anwendungsfall?

Abhängig von Ihrer Konfiguration ist das Löschen von Uploads, die eine gemeinsam genutzte Ressource sind (referenziert durch Beiträge, Benutzerprofil, Abzeichen usw.), ohne angemessene Sorgfalt etwas riskant.

Außerdem werden sie nach einer Weile automatisch bereinigt, wenn sie nirgendwo referenziert werden.

3 „Gefällt mir“

Der Anwendungsfall ist die zeitkritische Entfernung von Uploads als Teil von “Human in the Loop”-Automatisierungs-Workflows unter Verwendung von activepieces, Data Explorer und der API.

Das Ziel ist es, regulären Moderatoren eine einfache Möglichkeit zu geben, Uploads sofort und mit Sicherheit vollständig zu entfernen, ohne SSH-Zugriff zu benötigen, und alle bekannten Fälle für das Brechen von Upload-Referenzen umfassend abzudecken sowie automatisch die spezifischen URLs (und im Falle von proxied Avataren das URL-Präfix basierend auf dem Benutzernamen) aus dem CDN zu löschen (purgen).


Ich war erleichtert, als ich beim Testen sah, dass Avatar-, Profilhintergrund- und Kartenhintergrund-Referenzen automatisch behandelt werden, wenn ein Upload zerstört wird.

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

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


Die beiden Hauptszenarien wären “verbrannte Erde” (scorched earth) und “chirurgische Entfernung” (surgical removal). Dies ist die laufende Arbeit für “verbrannte Erde”:


Konvertierung in eine Upload-Hash-Liste:

  • Avatar-URLs (Benutzernamen mittels Regex extrahieren) → Avatar-Hash über Data Explorer-Abfrage abrufen (der Hash ist nicht in der URL enthalten)

  • Topic-/Post-URLs → Alle in diesem Beitrag verwendeten Upload-Hashes mittels Data Explorer sammeln

  • Direkte Original-/optimierte Upload-URLs (einschließlich Profilhintergrund und Kartenhintergrund) → Hashes mittels Regex extrahieren


Anschließend jede Hash abfragen, um alle Vorkommen zu finden (eine Data Explorer-Abfrage zur Abdeckung aller Fälle, jeweils ein Hash):

  • Benutzername/ID-Liste für Benutzer, die ihn für einen Avatar verwendet haben

  • Benutzername/ID-Liste für Benutzer, die ihn für einen Profilhintergrund verwendet haben

  • Benutzername/ID-Liste für Benutzer, die ihn für einen Kartenhintergrund verwendet haben

  • Liste aller Beiträge (Rohdaten), die diesen Upload verwenden


Aktionen:

  • Alle Benutzer sperren (suspend), die den Upload für einen Avatar, Profilhintergrund oder Kartenhintergrund verwendet haben

  • Alle Benutzer sperren, deren Beiträge auf den Ziel-Upload verweisen, aber diejenigen ausschließen, deren Verweis innerhalb eines Zitats verschachtelt ist

  • Alle Themen löschen

  • Alle Beiträge löschen

  • Upload zerstören (kein API-Endpunkt)

  • Alle CDN-URLs (optimiert/unoptimiert) löschen (purgen)

  • Das Standardpräfix für proxied Avatar-URLs für jeden zugehörigen Benutzernamen löschen (um alle Größen abzudecken)


Das Szenario der chirurgischen Entfernung wäre im Wesentlichen dasselbe, würde aber die zugehörigen Benutzer nicht sperren und erfordert einige Änderungen hinsichtlich des Sammelns aller Upload-Hashes aus Post-/Topic-URLs.

Wahrscheinlich würden die Beiträge/Themen selbst immer noch gelöscht, um fehlerhafte Referenzen zu vermeiden, aber das Entfernen der Upload-Markdown-Syntax für diesen spezifischen Upload (ohne andere Uploads zu berühren) wäre besser, falls möglich. Ähnlich wie bei dieser Automatisierung, wenn sie nicht alle Markdown-Upload-Referenzen entfernt hätte.


Idealerweise möchte ich auch Hashes blockieren können, sodass nach dem Durchlaufen des Obigen jemand nicht einfach ein neues Konto erstellen und erneut hochladen kann. :melting_face: :coffin:

Ich glaube nicht, dass dies derzeit für einen regulären Moderator möglich ist, z. B. durch die Verwendung von beobachteten Wörtern (watched words). Daher könnte das Durchführen eines periodischen Scans wie oben für eine Liste von Hashes eine Möglichkeit sein, dies zu handhaben.

1 „Gefällt mir“

Haben Sie sich unser Legal Compliance Plugin angesehen?

Es verfügt über Endpunkte, um die Uploads für einen Beitrag abzurufen und einen Upload anhand der Upload- und Beitrags-IDs zu entfernen.

Es erweitert auch die Suchfunktionalität, um alle Beiträge abzurufen, die einen bestimmten Upload enthalten, gegeben den Hash.

2 „Gefällt mir“

Ich verlinke dies nur für Benutzer, die Ihren Beitrag lesen und ihm zustimmen, sie könnten daran interessiert sein, dafür abzustimmen (ich weiß, dass Sie ihn bereits gelesen haben):

2 „Gefällt mir“

Ja! Ich habe fast Freudentränen vergossen, als ich Ihr Plugin Anfang des Monats gesehen habe, haha. Vielen, vielen Dank für die Erstellung und Weitergabe. :heart:

Einige Szenarien, die ich damit nicht abdecken konnte:

  • Verwendung des Uploads: Suchpräfix, es schien nicht möglich zu sein, Profilhintergründe oder Kartenhintergründe zu finden (es sei denn, jemand hat diese Bilder direkt in einem Beitrag verlinkt).
  • Hartes Löschen von Bildern in diesem Szenario (Bild ohne Zuordnung zu Beiträgen).
  • Ähnlich für Avatare, z. B. den Hash des Avatars abrufen (er befindet sich normalerweise nicht in der URL) und dann alle anderen Konten finden, die ihn möglicherweise ebenfalls verwendet haben, falls eine Sperrung erforderlich ist + den Upload ohne Beitragszuordnung löschen.

Nice to haves:

  • Auslösen eines Webhooks bei der Löschung, um CDN-Bereinigungen nach der Löschung für alle Varianten der Uploads auszulösen.
  • Massen-/automatisches Löschen aller Beiträge/Themen, die den Upload-Verweis enthalten, wenn der Upload gelöscht wird.

In Discourse (Web-UI) scheint es weder für einen Administrator/Moderator noch für den Benutzer selbst nicht möglich zu sein, einen Benutzeravatar zu löschen (zu entfernen) (außer durch Hochladen eines Ersatzes). Dies ist für den Profilhintergrund und das Banner möglich. Aber beide zerstören den Upload nicht sofort, und wenn ein anderer unbekannter Benutzer existiert, der denselben Upload für einen Teil seines Profils (Avatar, Profilhintergrund, Profilkarte) verwendet, wird er später auch nicht bereinigt.


Ich kam zu dem Schluss, dass es sich lohnen könnte, einige automatisierte Workflows zu erstellen, die den größten Teil oder den gesamten Prozess abdecken können (mit menschlicher Überprüfung/Genehmigung und/oder vereinfachter menschlicher Eingabe). Um die konsistente Anwendung zu erleichtern, Randfälle abzudecken und idealerweise die Möglichkeit zu minimieren, dass nicht gelöschte Beiträge tote Upload-Verweise aufweisen. Außerdem automatische Sperrung bei Bedarf (es sei denn, der Ziel-Upload befindet sich in einem Zitat) und CDN-Bereinigung.


Dies ist, was ich bisher für die Data Explorer-Abfragen hatte (noch nicht beim „Zusammenfügen“-Teil), mit dem Ziel, Folgendes abzudecken:

  • Themen-URLs
  • Beitrags-URLs
  • Direkte Bild-URLs (Beiträge, Profilhintergrund, Kartenhintergrund)
  • Avatar-URLs (proxied)

Liste der Upload-Hashes vorbereiten

Benutzername (aus proxied Avatar-URL) zu Upload-Hash

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

Beitrags-/Themen-URL zu Liste der Upload-Hashes

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

Die anderen mir bekannten Upload-URL-Typen haben den Hash in der URL selbst (es ist nicht erforderlich, den Data Explorer zu verwenden, um diese Hashes abzurufen).


Alle umsetzbaren Informationen zu einem Upload-Hash sammeln und vorbereiten

Beispielparameter:

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

cdn_url_prefix:
https://cdn.example.com

app_hostname:
www.example.com

upload_hash:
0000000000000000000000000000000000000000

Hash-Suche & Zusammenfassung

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

Deckt nicht ab: Themen/Beiträge in der Warteschlange und Entwürfe

Ausgaben:

upload_ids (sollte nur eins sein)
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

Dies liefert die erforderlichen Informationen, um diese APIs selektiv zu verwenden:

Und für weitere Aktionen:

  • Alle CDN-URLs bereinigen (alle Varianten, optimiert und Original usw.)
  • Präfix-URLs von proxied Avataren bereinigen (um alle Größen abzudecken)

Automatisch von Discourse behandelt:

  • Entfernt den Upload-Verweis sofort aus allen Benutzerprofilen (Avatar, Profilhintergrund und Kartenhintergrund), wenn ein Upload zerstört/gelöscht wird.
1 „Gefällt mir“