Skip to main content

alibi_sqlx/store/
device_codes.rs

1use super::SqlxStore;
2use super::entities::device_code::Model;
3use crate::error::record_not_updated;
4use crate::model::{self, ActiveRow, SqlxModel};
5use crate::schema::AuthSchema;
6use crate::sql::Sql;
7use crate::value::SqlValue;
8use alibi_core::error::{AuthError, AuthResult};
9use alibi_core::store::DeviceCodeStore;
10use alibi_core::types::{CreateDeviceCode, DeviceCode, UpdateDeviceCode};
11use async_trait::async_trait;
12use uuid::Uuid;
13
14impl<S: AuthSchema + Send + Sync> SqlxStore<S> {
15    async fn find_device_code(&self, column: &str, value: &str) -> AuthResult<Option<DeviceCode>> {
16        let mut sql = model::select_model::<Model>(self.exec());
17        sql.push(" WHERE ");
18        sql.compare(Model::TABLE, column, " = ", value);
19        model::limit_one(&mut sql);
20        Ok(self
21            .exec()
22            .fetch_optional::<Model>(sql)
23            .await?
24            .map(|model| DeviceCode::from(&model)))
25    }
26
27    /// `UPDATE device_code SET ... WHERE id = ? AND ...`, returning the affected count.
28    async fn update_device_codes(
29        &self,
30        sets: Vec<(&'static str, SqlValue)>,
31        guards: Vec<(&'static str, Option<SqlValue>)>,
32    ) -> AuthResult<u64> {
33        let mut sql = Sql::with(self.exec().engine(), "UPDATE ");
34        sql.ident(Model::TABLE);
35        sql.push(" SET ");
36        for (index, (column, value)) in sets.into_iter().enumerate() {
37            if index > 0 {
38                sql.push(", ");
39            }
40            sql.assign(column, value);
41        }
42        for (index, (column, value)) in guards.into_iter().enumerate() {
43            sql.push(if index == 0 { " WHERE " } else { " AND " });
44            sql.column(Model::TABLE, column);
45            match value {
46                Some(value) => {
47                    sql.push(" = ");
48                    sql.bind(value);
49                }
50                None => {
51                    sql.push(" IS NULL");
52                }
53            }
54        }
55        self.exec().execute(sql).await
56    }
57}
58
59#[async_trait]
60impl<S> DeviceCodeStore for SqlxStore<S>
61where
62    S: AuthSchema + Send + Sync,
63{
64    async fn create_device_code(&self, input: CreateDeviceCode) -> AuthResult<DeviceCode> {
65        let mut active = ActiveRow::new();
66        active.set("id", Uuid::new_v4().to_string());
67        active.set("device_code", input.device_code);
68        active.set("user_code", input.user_code);
69        active.set("user_id", input.user_id);
70        active.set("expires_at", input.expires_at);
71        active.set("status", input.status);
72        active.set("last_polled_at", input.last_polled_at);
73        active.set("polling_interval", input.polling_interval);
74        active.set("client_id", input.client_id);
75        active.set("scope", input.scope);
76        model::insert::<Model>(self.exec(), &active)
77            .await
78            .map(|model| DeviceCode::from(&model))
79    }
80
81    async fn get_device_code_by_device_code(
82        &self,
83        device_code: &str,
84    ) -> AuthResult<Option<DeviceCode>> {
85        self.find_device_code("device_code", device_code).await
86    }
87
88    async fn get_device_code_by_user_code(
89        &self,
90        user_code: &str,
91    ) -> AuthResult<Option<DeviceCode>> {
92        self.find_device_code("user_code", user_code).await
93    }
94
95    async fn update_device_code(
96        &self,
97        id: &str,
98        update: UpdateDeviceCode,
99    ) -> AuthResult<DeviceCode> {
100        let mut sql = model::by_id::<Model>(self.exec(), id);
101        model::limit_one(&mut sql);
102        let Some(model) = self.exec().fetch_optional::<Model>(sql).await? else {
103            return Err(AuthError::not_found("Device code not found"));
104        };
105
106        let mut active = model.into_active();
107        if let Some(status) = update.status {
108            active.set("status", status);
109        }
110        if let Some(user_id) = update.user_id {
111            active.set("user_id", user_id);
112        }
113        if let Some(last_polled_at) = update.last_polled_at {
114            active.set("last_polled_at", last_polled_at);
115        }
116
117        model::update::<Model>(self.exec(), &active)
118            .await?
119            .map(|model_2| DeviceCode::from(&model_2))
120            .ok_or_else(record_not_updated)
121    }
122
123    async fn update_device_code_if_status(
124        &self,
125        id: &str,
126        current_status: &str,
127        update: UpdateDeviceCode,
128    ) -> AuthResult<bool> {
129        let mut sets = Vec::new();
130        if let Some(status) = update.status {
131            sets.push(("status", status.into()));
132        }
133        if let Some(user_id) = update.user_id {
134            sets.push(("user_id", user_id.into()));
135        }
136        if let Some(last_polled_at) = update.last_polled_at {
137            sets.push(("last_polled_at", last_polled_at.into()));
138        }
139        self.update_device_codes(
140            sets,
141            vec![
142                ("id", Some(id.into())),
143                ("status", Some(current_status.into())),
144            ],
145        )
146        .await
147        .map(|affected| affected == 1)
148    }
149
150    async fn claim_device_code(&self, id: &str, user_id: &str) -> AuthResult<bool> {
151        self.update_device_codes(
152            vec![("user_id", user_id.into())],
153            vec![
154                ("id", Some(id.into())),
155                ("status", Some("pending".into())),
156                ("user_id", None),
157            ],
158        )
159        .await
160        .map(|affected| affected == 1)
161    }
162
163    async fn delete_device_code(&self, id: &str) -> AuthResult<()> {
164        self.exec()
165            .execute(model::delete_by_id::<Model>(self.exec(), id))
166            .await
167            .map(drop)
168    }
169
170    async fn delete_device_code_if_status(&self, id: &str, status: &str) -> AuthResult<bool> {
171        let mut sql = model::delete_by_id::<Model>(self.exec(), id);
172        sql.push(" AND ");
173        sql.compare(Model::TABLE, "status", " = ", status);
174        self.exec().execute(sql).await.map(|affected| affected == 1)
175    }
176}