//! Database storage implementations for badges, awards, and identities. //! //! Provides trait-based storage abstraction with SQLite and PostgreSQL backends, //! plus file storage for badge images with local and S3 support. use crate::errors::Result; use async_trait::async_trait; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use serde_json::Value; // Re-export storage implementations /// File storage implementations for badge images. pub mod file_storage; #[cfg(feature = "postgres")] /// PostgreSQL storage implementation. pub mod postgres; #[cfg(feature = "sqlite")] /// SQLite storage implementation. pub mod sqlite; #[cfg(feature = "s3")] pub use file_storage::S3FileStorage; pub use file_storage::{FileStorage, LocalFileStorage}; #[cfg(feature = "postgres")] pub use postgres::*; #[cfg(feature = "sqlite")] pub use sqlite::*; /// Identity information for a DID. #[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)] pub struct Identity { /// Decentralized identifier. pub did: String, /// AT Protocol handle. pub handle: String, /// Full DID document JSON. pub record: Value, /// Record creation timestamp. pub created_at: DateTime, /// Record last update timestamp. pub updated_at: DateTime, } /// Badge award record. #[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)] pub struct Award { /// AT-URI of the award record. pub aturi: String, /// Content identifier for the award record. pub cid: String, /// DID of the award recipient. pub did: String, /// AT-URI of the associated badge. pub badge: String, /// Content identifier of the badge record. pub badge_cid: String, /// Human-readable badge name. pub badge_name: String, /// JSON array of validated issuer DIDs. pub validated_issuers: Value, /// Record creation timestamp. pub created_at: DateTime, /// Record last update timestamp. pub updated_at: DateTime, /// Full JSON record of the award. pub record: Value, } /// Badge definition record. #[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)] pub struct Badge { /// AT-URI of the badge definition. pub aturi: String, /// Content identifier for the badge record. pub cid: String, /// Human-readable badge name. pub name: String, /// Optional image reference (blob CID). pub image: Option, /// Record creation timestamp. pub created_at: DateTime, /// Record last update timestamp. pub updated_at: DateTime, /// Number of awards using this badge. pub count: i64, /// Full JSON record of the badge definition. pub record: Value, } /// Award with enriched badge and identity information. #[derive(Debug, Clone)] pub struct AwardWithBadge { /// The award record. pub award: Award, /// Associated badge information if available. pub badge: Option, /// Recipient identity information if available. pub identity: Option, /// Identity information for award signers. pub signer_identities: Vec, } /// Storage trait defining the interface for badge storage operations #[async_trait] pub trait Storage: Send + Sync { /// Run database migrations async fn migrate(&self) -> Result<()>; /// Insert or update an identity async fn upsert_identity(&self, identity: &Identity) -> Result<()>; /// Get an identity by DID async fn get_identity_by_did(&self, did: &str) -> Result>; /// Get an identity by handle async fn get_identity_by_handle(&self, handle: &str) -> Result>; /// Insert or update a badge async fn upsert_badge(&self, badge: &Badge) -> Result<()>; /// Get a badge by AT-URI and CID async fn get_badge(&self, aturi: &str, cid: &str) -> Result>; /// Increment the count for a badge async fn increment_badge_count(&self, aturi: &str, cid: &str) -> Result<()>; /// Decrement the count for a badge async fn decrement_badge_count(&self, aturi: &str, cid: &str) -> Result<()>; /// Insert or update an award, returns true if it's a new award async fn upsert_award(&self, award: &Award) -> Result; /// Get an award by AT-URI async fn get_award(&self, aturi: &str) -> Result>; /// Delete an award by AT-URI async fn delete_award(&self, aturi: &str) -> Result>; /// Trim awards for a DID to keep only the most recent ones async fn trim_awards_for_did(&self, did: &str, max_count: i64) -> Result<()>; /// Get recent awards with enriched badge and identity information async fn get_recent_awards(&self, limit: i64) -> Result>; /// Get awards for a specific DID with enriched information async fn get_awards_for_did(&self, did: &str, limit: i64) -> Result>; } #[cfg(test)] mod tests { use super::*; use chrono::Utc; use serde_json::json; #[test] fn test_badge_with_record() { let record = json!({ "name": "Test Badge", "description": "A test badge for unit testing", "image": { "$type": "blob", "ref": {"$link": "bafkreiabc123"}, "mimeType": "image/png", "size": 1024 } }); let badge = Badge { aturi: "at://did:plc:test/community.lexicon.badge.definition/test".to_string(), cid: "bafyreiabc123".to_string(), name: "Test Badge".to_string(), image: Some("bafkreiabc123".to_string()), created_at: Utc::now(), updated_at: Utc::now(), count: 0, record: record.clone(), }; // Verify the badge contains the record assert_eq!(badge.record, record); assert_eq!(badge.name, "Test Badge"); assert_eq!(badge.record["name"], "Test Badge"); assert_eq!(badge.record["description"], "A test badge for unit testing"); } #[test] fn test_badge_record_serialization() { let record = json!({ "name": "Serialization Test", "description": "Testing JSON serialization", "issuer": "did:plc:issuer" }); let badge = Badge { aturi: "at://did:plc:test/community.lexicon.badge.definition/serial".to_string(), cid: "bafyreiserial".to_string(), name: "Serialization Test".to_string(), image: None, created_at: Utc::now(), updated_at: Utc::now(), count: 5, record: record.clone(), }; // Test that we can serialize and deserialize the badge let serialized = serde_json::to_string(&badge).expect("Failed to serialize badge"); let deserialized: Badge = serde_json::from_str(&serialized).expect("Failed to deserialize badge"); assert_eq!(deserialized.record, record); assert_eq!(deserialized.name, badge.name); assert_eq!(deserialized.count, badge.count); } }