Skip to main content

systemprompt_users/repository/
api_key.rs

1use 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 list_revoked_api_key_ids_for_user(&self, user_id: &UserId) -> Result<Vec<String>> {
93        let rows = sqlx::query_scalar!(
94            r#"
95            SELECT id
96            FROM user_api_keys
97            WHERE user_id = $1 AND revoked_at IS NOT NULL
98            ORDER BY revoked_at DESC
99            "#,
100            user_id.as_str(),
101        )
102        .fetch_all(&*self.pool)
103        .await?;
104        Ok(rows)
105    }
106
107    pub async fn touch_api_key_usage(&self, id: &ApiKeyId) -> Result<()> {
108        sqlx::query!(
109            r#"
110            UPDATE user_api_keys
111            SET last_used_at = CURRENT_TIMESTAMP
112            WHERE id = $1
113            "#,
114            id.as_str(),
115        )
116        .execute(&*self.write_pool)
117        .await?;
118        Ok(())
119    }
120}