systemprompt_users/repository/
api_key.rs1use chrono::{DateTime, Utc};
7use systemprompt_identifiers::{ApiKeyId, UserId};
8
9use crate::error::Result;
10use crate::models::UserApiKey;
11use crate::repository::UserRepository;
12
13pub struct CreateApiKeyParams<'a> {
14 pub id: &'a ApiKeyId,
15 pub user_id: &'a UserId,
16 pub name: &'a str,
17 pub key_prefix: &'a str,
18 pub key_hash: &'a str,
19 pub expires_at: Option<DateTime<Utc>>,
20}
21
22impl UserRepository {
23 pub async fn create_api_key(&self, params: CreateApiKeyParams<'_>) -> Result<UserApiKey> {
24 let row = sqlx::query_as!(
25 UserApiKey,
26 r#"
27 INSERT INTO user_api_keys
28 (id, user_id, name, key_prefix, key_hash, expires_at)
29 VALUES ($1, $2, $3, $4, $5, $6)
30 RETURNING id, user_id, name, key_prefix, key_hash,
31 created_at, last_used_at, expires_at, revoked_at
32 "#,
33 params.id.as_str(),
34 params.user_id.as_str(),
35 params.name,
36 params.key_prefix,
37 params.key_hash,
38 params.expires_at,
39 )
40 .fetch_one(&*self.write_pool)
41 .await?;
42 Ok(row)
43 }
44
45 pub async fn find_active_api_key_by_prefix(
46 &self,
47 key_prefix: &str,
48 ) -> Result<Option<UserApiKey>> {
49 let row = sqlx::query_as!(
50 UserApiKey,
51 r#"
52 SELECT id, user_id, name, key_prefix, key_hash,
53 created_at, last_used_at, expires_at, revoked_at
54 FROM user_api_keys
55 WHERE key_prefix = $1
56 AND revoked_at IS NULL
57 "#,
58 key_prefix,
59 )
60 .fetch_optional(&*self.pool)
61 .await?;
62 Ok(row)
63 }
64
65 pub async fn list_api_keys_for_user(&self, user_id: &UserId) -> Result<Vec<UserApiKey>> {
66 let rows = sqlx::query_as!(
67 UserApiKey,
68 r#"
69 SELECT id, user_id, name, key_prefix, key_hash,
70 created_at, last_used_at, expires_at, revoked_at
71 FROM user_api_keys
72 WHERE user_id = $1
73 ORDER BY created_at DESC
74 "#,
75 user_id.as_str(),
76 )
77 .fetch_all(&*self.pool)
78 .await?;
79 Ok(rows)
80 }
81
82 pub async fn revoke_api_key(&self, id: &ApiKeyId, user_id: &UserId) -> Result<bool> {
83 let result = sqlx::query!(
84 r#"
85 UPDATE user_api_keys
86 SET revoked_at = CURRENT_TIMESTAMP
87 WHERE id = $1 AND user_id = $2 AND revoked_at IS NULL
88 "#,
89 id.as_str(),
90 user_id.as_str(),
91 )
92 .execute(&*self.write_pool)
93 .await?;
94 Ok(result.rows_affected() > 0)
95 }
96
97 pub async fn list_revoked_api_key_ids_for_user(&self, user_id: &UserId) -> Result<Vec<String>> {
98 let rows = sqlx::query_scalar!(
99 r#"
100 SELECT id
101 FROM user_api_keys
102 WHERE user_id = $1 AND revoked_at IS NOT NULL
103 ORDER BY revoked_at DESC
104 "#,
105 user_id.as_str(),
106 )
107 .fetch_all(&*self.pool)
108 .await?;
109 Ok(rows)
110 }
111
112 pub async fn touch_api_key_usage(&self, id: &ApiKeyId) -> Result<()> {
113 sqlx::query!(
114 r#"
115 UPDATE user_api_keys
116 SET last_used_at = CURRENT_TIMESTAMP
117 WHERE id = $1
118 "#,
119 id.as_str(),
120 )
121 .execute(&*self.write_pool)
122 .await?;
123 Ok(())
124 }
125}