yggdrasil/api/assets/
mod.rs1#![allow(clippy::unused_unit, deprecated, unused_imports)]
8
9#[cfg(feature = "server")]
14pub(crate) const ASSET_REF_CLAUSE: &str =
15 "(EXISTS (SELECT 1 FROM asset_refs r WHERE r.asset_id = a.id) \
16 OR EXISTS (SELECT 1 FROM note_asset_refs r WHERE r.asset_id = a.id) \
17 OR EXISTS (SELECT 1 FROM comments c \
18 WHERE c.deleted_at IS NULL AND c.content_html LIKE '%' || a.path || '%') \
19 OR EXISTS (SELECT 1 FROM users u WHERE u.avatar_url = '/uploads/' || a.path) \
20 OR EXISTS (SELECT 1 FROM friend_links f WHERE f.avatar_url = '/uploads/' || a.path))";
21
22#[cfg(feature = "server")]
24pub(crate) const COMMENT_REF_CLAUSE: &str = "EXISTS (SELECT 1 FROM comments c \
25 WHERE c.deleted_at IS NULL AND c.content_html LIKE '%' || a.path || '%')";
26
27#[cfg(feature = "server")]
29pub(crate) const AVATAR_REF_CLAUSE: &str =
30 "(EXISTS (SELECT 1 FROM users u WHERE u.avatar_url = '/uploads/' || a.path) \
31 OR EXISTS (SELECT 1 FROM friend_links f WHERE f.avatar_url = '/uploads/' || a.path))";
32
33#[cfg(feature = "server")]
35pub(crate) const ASSET_REF_COUNT_EXPR: &str =
36 "((SELECT COUNT(*) FROM asset_refs r WHERE r.asset_id = a.id) + \
37 (SELECT COUNT(DISTINCT note_id) FROM note_asset_refs r WHERE r.asset_id = a.id) + \
38 (SELECT COUNT(*) FROM comments c WHERE c.deleted_at IS NULL \
39 AND c.content_html LIKE '%' || a.path || '%') + \
40 (SELECT COUNT(*) FROM users u WHERE u.avatar_url = '/uploads/' || a.path) + \
41 (SELECT COUNT(*) FROM friend_links f WHERE f.avatar_url = '/uploads/' || a.path))";
42
43pub mod delete;
45pub mod list;
47pub mod rebuild;
49pub mod types;
51
52pub use delete::{batch_delete_assets, delete_asset, purge_orphan_assets, update_asset_alt};
53pub use list::list_assets;
54pub use rebuild::rebuild_assets_index;
55pub use types::{
56 AssetListResponse, AssetOpResponse, BatchDeleteAssetsResponse, PurgeOrphansResponse,
57 RebuildAssetsResponse,
58};
59
60#[cfg(all(test, feature = "server"))]
61mod tests {
62 use super::ASSET_REF_CLAUSE;
63
64 #[test]
65 fn asset_reference_clause_protects_profile_avatar() {
66 assert!(
67 ASSET_REF_CLAUSE.contains("users u")
68 && ASSET_REF_CLAUSE.contains("u.avatar_url = '/uploads/' || a.path"),
69 "素材引用判定必须包含用户头像路径"
70 );
71 }
72}