1use std::future::Future;
2use std::pin::Pin;
3
4use crate::db::{ColumnMap, ConnExt, ConnQueryExt, Database, FromRow};
5use crate::error::Result;
6
7use super::backend::ApiKeyBackend;
8use super::types::ApiKeyRecord;
9
10pub(crate) struct SqliteBackend {
11 db: Database,
12}
13
14impl SqliteBackend {
15 pub fn new(db: Database) -> Self {
16 Self { db }
17 }
18}
19
20impl FromRow for ApiKeyRecord {
21 fn from_row(row: &libsql::Row) -> Result<Self> {
22 let cols = ColumnMap::from_row(row);
23 let scopes_json: String = cols.get(row, "scopes")?;
24 let scopes: Vec<String> = serde_json::from_str(&scopes_json)
25 .map_err(|e| crate::Error::internal(format!("deserialize api_keys.scopes: {e}")))?;
26
27 Ok(Self {
28 id: cols.get(row, "id")?,
29 key_hash: cols.get(row, "key_hash")?,
30 tenant_id: cols.get(row, "tenant_id")?,
31 name: cols.get(row, "name")?,
32 scopes,
33 expires_at: cols.get(row, "expires_at")?,
34 last_used_at: cols.get(row, "last_used_at")?,
35 created_at: cols.get(row, "created_at")?,
36 revoked_at: cols.get(row, "revoked_at")?,
37 })
38 }
39}
40
41impl ApiKeyBackend for SqliteBackend {
42 fn store(
43 &self,
44 record: &ApiKeyRecord,
45 ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + '_>> {
46 let id = record.id.clone();
47 let key_hash = record.key_hash.clone();
48 let tenant_id = record.tenant_id.clone();
49 let name = record.name.clone();
50 let scopes = serde_json::to_string(&record.scopes).unwrap();
52 let expires_at = record.expires_at.clone();
53 let created_at = record.created_at.clone();
54
55 Box::pin(async move {
56 self.db
57 .conn()
58 .execute_raw(
59 "INSERT INTO api_keys (id, key_hash, tenant_id, name, scopes, expires_at, created_at) \
60 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
61 libsql::params![id, key_hash, tenant_id, name, scopes, expires_at, created_at],
62 )
63 .await
64 .map_err(crate::Error::from)?;
65 Ok(())
66 })
67 }
68
69 fn lookup(
70 &self,
71 key_id: &str,
72 ) -> Pin<Box<dyn Future<Output = Result<Option<ApiKeyRecord>>> + Send + '_>> {
73 let key_id = key_id.to_owned();
74 Box::pin(async move {
75 self.db
76 .conn()
77 .query_optional::<ApiKeyRecord>(
78 "SELECT id, key_hash, tenant_id, name, scopes, expires_at, \
79 last_used_at, created_at, revoked_at \
80 FROM api_keys WHERE id = ?1",
81 libsql::params![key_id],
82 )
83 .await
84 })
85 }
86
87 fn revoke(
88 &self,
89 key_id: &str,
90 revoked_at: &str,
91 ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + '_>> {
92 let key_id = key_id.to_owned();
93 let revoked_at = revoked_at.to_owned();
94 Box::pin(async move {
95 self.db
96 .conn()
97 .execute_raw(
98 "UPDATE api_keys SET revoked_at = ?1 WHERE id = ?2",
99 libsql::params![revoked_at, key_id],
100 )
101 .await
102 .map_err(crate::Error::from)?;
103 Ok(())
104 })
105 }
106
107 fn list(
108 &self,
109 tenant_id: &str,
110 ) -> Pin<Box<dyn Future<Output = Result<Vec<ApiKeyRecord>>> + Send + '_>> {
111 let tenant_id = tenant_id.to_owned();
112 Box::pin(async move {
113 self.db
114 .conn()
115 .query_all::<ApiKeyRecord>(
116 "SELECT id, key_hash, tenant_id, name, scopes, expires_at, \
117 last_used_at, created_at, revoked_at \
118 FROM api_keys WHERE tenant_id = ?1 AND revoked_at IS NULL \
119 ORDER BY created_at DESC",
120 libsql::params![tenant_id],
121 )
122 .await
123 })
124 }
125
126 fn update_last_used(
127 &self,
128 key_id: &str,
129 timestamp: &str,
130 ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + '_>> {
131 let key_id = key_id.to_owned();
132 let timestamp = timestamp.to_owned();
133 Box::pin(async move {
134 self.db
135 .conn()
136 .execute_raw(
137 "UPDATE api_keys SET last_used_at = ?1 WHERE id = ?2",
138 libsql::params![timestamp, key_id],
139 )
140 .await
141 .map_err(crate::Error::from)?;
142 Ok(())
143 })
144 }
145
146 fn update_expires_at(
147 &self,
148 key_id: &str,
149 expires_at: Option<&str>,
150 ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + '_>> {
151 let key_id = key_id.to_owned();
152 let expires_at = expires_at.map(|s| s.to_owned());
153 Box::pin(async move {
154 self.db
155 .conn()
156 .execute_raw(
157 "UPDATE api_keys SET expires_at = ?1 WHERE id = ?2",
158 libsql::params![expires_at, key_id],
159 )
160 .await
161 .map_err(crate::Error::from)?;
162 Ok(())
163 })
164 }
165}