Intenté acceder a él, pero parece que no existe un endpoint de la API para eliminar una subida no existe.
Lo publico aquí porque no pude encontrar ninguna mención o sugerencia al respecto; lo más parecido que encontré fue: API: a scope with access to /uploads
En la configuración de alcance granular de la clave de API para las subidas, veo que ‘create’ (crear) está disponible, pero ‘delete’ (eliminar) no lo está.
Parece que una posible solución alternativa podría ser crear una automatización personalizada y activarla a través de la API.
Dependiendo de su configuración, dado que las subidas son un recurso compartido (referenciado por publicaciones, perfil de usuario, insignias, etc.), es algo arriesgado eliminarlas sin la debida diligencia.
Además, se limpian automáticamente después de un tiempo si no están siendo referenciadas en ninguna parte.
El caso de uso es para la eliminación urgente de subidas como parte de flujos de trabajo de automatización con intervención humana utilizando activepieces, data explorer y la API.
El objetivo es permitir que los moderadores habituales tengan acceso a una forma sencilla de eliminar de forma segura y completa las subidas inmediatamente sin acceso SSH y cubrir exhaustivamente todos los casos conocidos de rotura de referencias de subidas, además de purgar automáticamente la CDN de las URL específicas (y para el caso de avatares proxificados, el prefijo de URL basado en el nombre de usuario).
Me sentí aliviado al ver en las pruebas que las referencias de avatares, fondos de perfil y fondos de tarjeta se manejan automáticamente cuando se destruye una subida.
Los dos escenarios principales serían ‘tierra quemada’ y ‘eliminación quirúrgica’. Este es el trabajo en curso para tierra quemada:
Conversión a una lista de hashes de subidas:
URL de avatar (extraer nombre de usuario usando regex) → obtener hash de avatar mediante consulta de data explorer (el hash no está en la URL)
URL de tema/publicación → recopilar todos los hashes de subida utilizados en esa publicación usando data explorer
URL de subida original/optimizada directa (incluido el fondo de perfil y el fondo de tarjeta) → extraer hashes usando regex
Luego consultar cada hash para encontrar todas las ocurrencias (una consulta de data explorer para cubrir todos los casos, un hash a la vez):
lista de nombre de usuario/ID, para usuarios que lo usaron como avatar
lista de nombre de usuario/ID, para usuarios que lo usaron como fondo de perfil
lista de nombre de usuario/ID, para usuarios que lo usaron como fondo de tarjeta
lista de todas las publicaciones (raw) que usan esta subida
Acciones:
suspender a todos los usuarios que usaron la subida para un avatar, fondo de perfil o fondo de tarjeta
suspender a todos los usuarios que tienen publicaciones que hacen referencia a la subida objetivo, pero excluir si su referencia está anidada dentro de una cita
eliminar todos los temas
eliminar todas las publicaciones
destruir subida (no hay endpoint de API)
purgar todas las URL de CDN (optimizadas/no optimizadas)
purgar el prefijo estándar para las URL de avatar proxificadas para cada nombre de usuario asociado (para cubrir todos los tamaños)
El escenario de eliminación quirúrgica sería esencialmente el mismo, pero sin suspender a los usuarios asociados y necesitaría algunos cambios con respecto a la recopilación de todos los hashes de subida de las URL de publicaciones/temas.
Probablemente seguiría siendo necesario eliminar las publicaciones/temas en sí para evitar referencias rotas, pero eliminar el markdown de la subida para esa subida específica (sin tocar otras subidas) sería superior si es posible. Como esta automatización si no eliminara todas las referencias de subidas en markdown.
Idealmente, me gustaría poder bloquear también los hashes, de modo que después de pasar por lo anterior, alguien no pueda simplemente crear una nueva cuenta y volver a subir.
No creo que sea posible actualmente para un moderador normal, como usando palabras vigiladas. Así que quizás realizar un escaneo periódico como el anterior para una lista de hashes sería una forma de manejar eso.
Tiene endpoints para obtener las subidas (uploads) de una publicación y para eliminar una subida dados los identificadores (IDs) de la subida y la publicación.
También extiende la funcionalidad de búsqueda para obtener todas las publicaciones que contienen una subida específica, dado el hash.
Solo estoy enlazando esto para los usuarios que lean y estén de acuerdo con tu publicación, podrían estar interesados en votar por esto (sé que ya lo leíste):
¡Sí! Lloré lágrimas de alegría al ver tu plugin a principios de este mes, jaja. Muchísimas gracias por crearlo y compartirlo.
Algunos escenarios que no encontré cómo cubrir con él:
Usando la subida: prefijo de búsqueda, no parecía posible encontrar fondos de perfil o fondos de tarjeta (a menos que alguien vinculara directamente esas imágenes en un post también).
Eliminar permanentemente imágenes en ese escenario (imagen sin asociación con posts).
Similar para el Avatar, por ejemplo, obtener el hash del avatar (normalmente no está en la URL), luego encontrar cualquier otra cuenta que posiblemente también lo haya usado, si se necesita una suspensión + eliminar la subida sin asociación de post.
Cosas agradables de tener:
Activar un webhook tras la eliminación, para activar purgas de CDN después de la eliminación para todas las variantes de las subidas.
Eliminar masivamente/automáticamente todos los posts/topics que tengan la referencia de subida cuando se elimina la subida.
En Discourse (interfaz web), no parece posible eliminar (desvincular) el avatar de un usuario, ni por un administrador/moderador ni por el propio usuario (además de subir un reemplazo). Es posible para el fondo de perfil y el banner. Pero ambos no destruyen la subida inmediatamente y si existe otro usuario desconocido que usa la misma subida para una parte de su perfil (avatar, fondo de perfil, tarjeta de perfil) tampoco se purgará más tarde.
Terminé pensando que podría valer la pena intentar crear algunos flujos de trabajo automatizados que puedan manejar la mayor parte o todo el proceso (con revisión/aprobación humana y/o entrada humana simplificada). Para facilitar su aplicación de manera consistente, cubrir casos extremos e idealmente minimizar la posibilidad de tener posts no eliminados que tengan referencias de subida muertas. También suspender automáticamente si es necesario (a menos que la subida objetivo esté dentro de una cita) y purgar la CDN.
Esto es lo que tenía hasta ahora para las consultas del explorador de datos (aún no en la parte de ‘unirlas’), con el objetivo de cubrir:
URLs de temas (topic urls)
URLs de posts
URLs de imágenes directas (fondo de post, fondo de perfil, fondo de tarjeta)
URLs de avatares (proxificadas)
Preparar Lista de Hashes de Subidas
nombredeusuario (username) (desde URL de avatar proxificada) a hash de subida
-- [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 post/topic a lista de hashes de subida
-- [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'
Los otros tipos de URL de subida de los que estoy al tanto, tienen el hash en la propia URL (no es necesario usar el explorador de datos para obtener esos hashes).
Recopilar y preparar toda la información procesable sobre un hash de subida
-- [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
No cubre: topics en cola de revisión pendientes o posts en borrador
Suspender un usuario (para posts, se puede usar raw en post_details para excluir condicionalmente a los usuarios si el markdown de la subida está dentro de una cita)
Eliminación de Subida (la API no existe actualmente)
Y para acciones adicionales:
Purgar todas las URLs de CDN (todas las variantes, optimizadas y originales, etc.)
Purgar prefijos de URL de avatares proxificados (para cubrir todos los tamaños)
Manejado automáticamente por Discourse:
Elimina la referencia de subida de todos los perfiles de usuario (avatar, fondo de perfil y fondo de tarjeta) inmediatamente cuando se destruye/elimina una subida.