1use codlet_core::admin::{CodeAdminStore, CodeListFilter, CodeMeta};
7use codlet_core::hashing::KeyVersion;
8use codlet_core::secret::{CodeId, SubjectId};
9use codlet_core::store::error::StoreError;
10
11use crate::SqliteStore;
12
13type AdminRow = (
17 String, String, Option<String>, Option<String>, Option<String>, i64, i64, Option<i64>, Option<String>, Option<i64>, );
28
29fn row_to_meta(row: AdminRow) -> CodeMeta {
30 let (id, kv, purpose, scope, grant, created_at, expires_at, used_at, used_by, revoked_at) = row;
31 CodeMeta {
32 id: CodeId::new(id),
33 key_version: KeyVersion::new(kv),
34 purpose,
35 scope,
36 grant,
37 created_at: Some(created_at as u64),
38 expires_at: expires_at as u64,
39 used_at: used_at.map(|t| t as u64),
40 used_by: used_by.map(SubjectId::new),
41 revoked_at: revoked_at.map(|t| t as u64),
42 }
43}
44
45impl CodeAdminStore for SqliteStore {
46 async fn list_codes(
47 &self,
48 filter: &CodeListFilter,
49 now: u64,
50 ) -> Result<Vec<CodeMeta>, StoreError> {
51 let now_i = now as i64;
52
53 let rows: Vec<AdminRow> = match (&filter.scope, filter.active_only, filter.limit) {
54 (Some(scope), true, limit) => {
55 let mut rows: Vec<AdminRow> = sqlx::query_as(
56 "SELECT id, key_version, purpose, scope, grant_payload,
57 created_at, expires_at, used_at, used_by_subject, revoked_at
58 FROM codlet_codes
59 WHERE scope = ?
60 AND used_at IS NULL
61 AND revoked_at IS NULL
62 AND expires_at > ?
63 ORDER BY expires_at DESC",
64 )
65 .bind(scope.as_str())
66 .bind(now_i)
67 .fetch_all(&self.pool)
68 .await
69 .map_err(|e| StoreError::Backend(e.to_string()))?;
70 if let Some(n) = limit {
71 rows.truncate(n);
72 }
73 rows
74 }
75 (Some(scope), false, limit) => {
76 let mut rows: Vec<AdminRow> = sqlx::query_as(
77 "SELECT id, key_version, purpose, scope, grant_payload,
78 created_at, expires_at, used_at, used_by_subject, revoked_at
79 FROM codlet_codes
80 WHERE scope = ?
81 ORDER BY expires_at DESC",
82 )
83 .bind(scope.as_str())
84 .fetch_all(&self.pool)
85 .await
86 .map_err(|e| StoreError::Backend(e.to_string()))?;
87 if let Some(n) = limit {
88 rows.truncate(n);
89 }
90 rows
91 }
92 (None, true, limit) => {
93 let mut rows: Vec<AdminRow> = sqlx::query_as(
94 "SELECT id, key_version, purpose, scope, grant_payload,
95 created_at, expires_at, used_at, used_by_subject, revoked_at
96 FROM codlet_codes
97 WHERE used_at IS NULL
98 AND revoked_at IS NULL
99 AND expires_at > ?
100 ORDER BY expires_at DESC",
101 )
102 .bind(now_i)
103 .fetch_all(&self.pool)
104 .await
105 .map_err(|e| StoreError::Backend(e.to_string()))?;
106 if let Some(n) = limit {
107 rows.truncate(n);
108 }
109 rows
110 }
111 (None, false, limit) => {
112 let mut rows: Vec<AdminRow> = sqlx::query_as(
113 "SELECT id, key_version, purpose, scope, grant_payload,
114 created_at, expires_at, used_at, used_by_subject, revoked_at
115 FROM codlet_codes
116 ORDER BY expires_at DESC",
117 )
118 .fetch_all(&self.pool)
119 .await
120 .map_err(|e| StoreError::Backend(e.to_string()))?;
121 if let Some(n) = limit {
122 rows.truncate(n);
123 }
124 rows
125 }
126 };
127
128 Ok(rows.into_iter().map(row_to_meta).collect())
129 }
130
131 async fn get_code_meta(&self, code_id: &CodeId) -> Result<Option<CodeMeta>, StoreError> {
132 let row: Option<AdminRow> = sqlx::query_as(
133 "SELECT id, key_version, purpose, scope, grant_payload,
134 created_at, expires_at, used_at, used_by_subject, revoked_at
135 FROM codlet_codes
136 WHERE id = ?
137 LIMIT 1",
138 )
139 .bind(code_id.as_str())
140 .fetch_optional(&self.pool)
141 .await
142 .map_err(|e| StoreError::Backend(e.to_string()))?;
143
144 Ok(row.map(row_to_meta))
145 }
146}