Skip to main content

secrets_storage_postgres/
lib.rs

1use async_trait::async_trait;
2use secrets_core::storage::{StorageBackend, StorageEntry, StorageError, StorageResult};
3use sqlx::PgPool;
4
5pub struct PgStorage {
6    pool: PgPool,
7}
8
9impl PgStorage {
10    pub async fn connect(database_url: &str) -> Result<Self, sqlx::Error> {
11        let pool = PgPool::connect(database_url).await?;
12        sqlx::migrate!("./src/migrations").run(&pool).await?;
13        Ok(Self { pool })
14    }
15}
16
17#[async_trait]
18impl StorageBackend for PgStorage {
19    async fn get(&self, path: &str) -> StorageResult<Option<StorageEntry>> {
20        let row = sqlx::query_as::<_, (Vec<u8>, Option<chrono::DateTime<chrono::Utc>>)>(
21            "SELECT value, expires_at FROM kv_store WHERE path = $1",
22        )
23        .bind(path)
24        .fetch_optional(&self.pool)
25        .await
26        .map_err(|e| StorageError::Backend(e.to_string()))?;
27
28        Ok(row.map(|(value, expires_at)| StorageEntry { value, expires_at }))
29    }
30
31    async fn put(&self, path: &str, entry: StorageEntry) -> StorageResult<()> {
32        sqlx::query(
33            "INSERT INTO kv_store (path, value, expires_at, updated_at)
34             VALUES ($1, $2, $3, now())
35             ON CONFLICT (path) DO UPDATE SET value = $2, expires_at = $3, updated_at = now()",
36        )
37        .bind(path)
38        .bind(entry.value)
39        .bind(entry.expires_at)
40        .execute(&self.pool)
41        .await
42        .map_err(|e| StorageError::Backend(e.to_string()))?;
43        Ok(())
44    }
45
46    async fn delete(&self, path: &str) -> StorageResult<()> {
47        sqlx::query("DELETE FROM kv_store WHERE path = $1")
48            .bind(path)
49            .execute(&self.pool)
50            .await
51            .map_err(|e| StorageError::Backend(e.to_string()))?;
52        Ok(())
53    }
54
55    async fn list(&self, prefix: &str) -> StorageResult<Vec<String>> {
56        let rows: Vec<(String,)> =
57            sqlx::query_as("SELECT path FROM kv_store WHERE path LIKE $1 || '%'")
58                .bind(prefix)
59                .fetch_all(&self.pool)
60                .await
61                .map_err(|e| StorageError::Backend(e.to_string()))?;
62        Ok(rows.into_iter().map(|(p,)| p).collect())
63    }
64}