Last active 1696598176

rootspring's Avatar rootspring revised this gist 1696598176. Go to revision

1 file changed, 56 insertions

assetcleaner.rs(file created)

@@ -0,0 +1,56 @@
1 + use log::{info, warn, error};
2 +
3 + pub async fn asset_cleaner(pool: &sqlx::PgPool) -> Result<(), crate::Error> {
4 + let type_id_map = indexmap::indexmap! {
5 + "bots" => "bot_id",
6 + "servers" => "server_id",
7 + "teams" => "id",
8 + "partners" => "id",
9 + };
10 +
11 + let assets = ["avatars", "banners"];
12 +
13 + let Some(cdn_path) = crate::config::CONFIG.panel.cdn_scopes.get(&crate::config::CONFIG.panel.main_scope) else {
14 + return Err("No CDN scope for main scope".into());
15 + };
16 +
17 + // Enumerate over every possbility
18 + for asset in assets {
19 + for (entity_type, id_column) in &type_id_map {
20 + let entity_type_dir = format!("{}/{}/{}", cdn_path.path, asset, entity_type);
21 +
22 + if let Err(e) = std::fs::metadata(&entity_type_dir) {
23 + if e.kind() != std::io::ErrorKind::NotFound {
24 + error!("Could not validate '{}': {}", entity_type_dir, e);
25 + }
26 + continue;
27 + }
28 +
29 + info!("Validating '{}' for entity type '{}'", asset, entity_type);
30 +
31 + let dir = std::fs::read_dir(&entity_type_dir)?;
32 +
33 + for entry in dir {
34 + let entry = entry?;
35 + let file_name = entry.file_name().into_string().unwrap();
36 + let file_path = entry.path();
37 +
38 + let Some(id) = file_name.split('.').next() else {
39 + warn!("Invalid file name: {}", file_name);
40 + std::fs::remove_file(&file_path)?;
41 + continue;
42 + };
43 +
44 + let query = format!("SELECT {}::text FROM {} WHERE {}::text = $1::text", id_column, entity_type, id_column);
45 + let id: Option<String> = sqlx::query_scalar(&query).bind(id).fetch_optional(pool).await?;
46 +
47 + if id.is_none() {
48 + warn!("Found orphaned file: {}", file_path.display());
49 + std::fs::remove_file(&file_path)?;
50 + }
51 + }
52 + }
53 + }
54 +
55 + Ok(())
56 + }
Newer Older