alibi_sqlx/store/
device_codes.rs1use 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 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}