Skip to main content

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}