blossom_rs/db/mod.rs
1//! Database backends for blob metadata persistence.
2//!
3//! The [`BlobDatabase`] trait abstracts over metadata storage (upload records,
4//! user quotas, file statistics). Blob data itself lives in [`BlobBackend`](crate::storage::BlobBackend);
5//! the database only tracks metadata.
6
7mod memory;
8
9#[cfg(feature = "db-sqlite")]
10mod sqlite;
11
12#[cfg(feature = "db-postgres")]
13mod postgres;
14
15pub use memory::MemoryDatabase;
16
17#[cfg(feature = "db-sqlite")]
18pub use sqlite::SqliteDatabase;
19
20#[cfg(feature = "db-postgres")]
21pub use postgres::PostgresDatabase;
22
23use serde::{Deserialize, Serialize};
24
25/// Metadata record for an uploaded blob.
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct UploadRecord {
28 /// SHA256 hex hash of the blob content.
29 pub sha256: String,
30 /// Size in bytes.
31 pub size: u64,
32 /// MIME type (e.g., `application/octet-stream`).
33 pub mime_type: String,
34 /// Hex-encoded x-only public key of the uploader.
35 pub pubkey: String,
36 /// Unix timestamp of upload.
37 pub created_at: u64,
38 /// Perceptual hash for image deduplication (optional).
39 #[serde(skip_serializing_if = "Option::is_none", default)]
40 pub phash: Option<u64>,
41}
42
43/// Per-user record with role and quota tracking.
44#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct UserRecord {
46 /// Hex-encoded x-only public key.
47 pub pubkey: String,
48 /// Role: "admin", "member", or "denied". Default: "member".
49 #[serde(default = "default_role")]
50 pub role: String,
51 /// Maximum bytes this user may store. `None` means unlimited.
52 pub quota_bytes: Option<u64>,
53 /// Current total bytes stored by this user.
54 pub used_bytes: u64,
55}
56
57fn default_role() -> String {
58 "member".to_string()
59}
60
61/// Per-blob access statistics.
62#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct FileStats {
64 /// SHA256 hex hash.
65 pub sha256: String,
66 /// Total egress bytes served.
67 pub egress_bytes: u64,
68 /// Unix timestamp of last access.
69 pub last_accessed: u64,
70}
71
72/// Errors from database operations.
73#[derive(Debug, thiserror::Error)]
74pub enum DbError {
75 #[error("quota exceeded: used {used} + {requested} > limit {limit}")]
76 QuotaExceeded {
77 used: u64,
78 requested: u64,
79 limit: u64,
80 },
81 #[error("not found")]
82 NotFound,
83 #[error("database error: {0}")]
84 Internal(String),
85}
86
87/// Trait for blob metadata persistence.
88///
89/// Implementations store upload records, user quotas, and access statistics.
90/// All methods are synchronous; the server wraps in `Arc<Mutex<>>` like `BlobBackend`.
91pub trait BlobDatabase: Send + Sync {
92 // --- Upload records ---
93
94 /// Record a new upload. If the sha256 already exists for this pubkey, this is a no-op.
95 fn record_upload(&mut self, record: &UploadRecord) -> Result<(), DbError>;
96
97 /// Get the upload record for a blob.
98 fn get_upload(&self, sha256: &str) -> Result<UploadRecord, DbError>;
99
100 /// List uploads by a pubkey, ordered by created_at descending.
101 fn list_uploads_by_pubkey(&self, pubkey: &str) -> Result<Vec<UploadRecord>, DbError>;
102
103 /// Delete an upload record. Returns true if it existed.
104 fn delete_upload(&mut self, sha256: &str) -> Result<bool, DbError>;
105
106 // --- User / quota ---
107
108 /// Get or create a user record.
109 fn get_or_create_user(&mut self, pubkey: &str) -> Result<UserRecord, DbError>;
110
111 /// Set a user's quota limit. Pass `None` for unlimited.
112 fn set_quota(&mut self, pubkey: &str, quota_bytes: Option<u64>) -> Result<(), DbError>;
113
114 /// Check if a user can upload `additional_bytes` within their quota.
115 /// Returns `Ok(())` if allowed, `Err(DbError::QuotaExceeded)` if not.
116 fn check_quota(&self, pubkey: &str, additional_bytes: u64) -> Result<(), DbError>;
117
118 /// Update a user's used_bytes (called after upload or delete).
119 fn update_used_bytes(&mut self, pubkey: &str, used_bytes: u64) -> Result<(), DbError>;
120
121 // --- File statistics ---
122
123 /// Record an access event (download) for a blob.
124 fn record_access(&mut self, sha256: &str, bytes_served: u64) -> Result<(), DbError>;
125
126 /// Get statistics for a blob.
127 fn get_stats(&self, sha256: &str) -> Result<FileStats, DbError>;
128
129 /// Total number of upload records.
130 fn upload_count(&self) -> usize;
131
132 /// Total number of registered users.
133 fn user_count(&self) -> usize;
134
135 // --- Roles ---
136
137 /// Set a user's role ("admin", "member", or "denied").
138 /// Creates the user if they don't exist.
139 fn set_role(&mut self, pubkey: &str, role: &str) -> Result<(), DbError>;
140
141 /// Get a user's role. Returns "member" for unknown users.
142 fn get_role(&self, pubkey: &str) -> String;
143
144 /// List all users with a given role.
145 fn list_users_by_role(&self, role: &str) -> Result<Vec<UserRecord>, DbError>;
146
147 // --- Perceptual hash dedup ---
148
149 /// Find uploads with a matching perceptual hash (for image dedup).
150 /// Returns uploads whose phash matches within a Hamming distance threshold.
151 fn find_by_phash(&self, phash: u64) -> Result<Vec<UploadRecord>, DbError> {
152 // Default implementation: no phash support.
153 let _ = phash;
154 Ok(vec![])
155 }
156}