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