use std::sync::Arc;
use sqlx::sqlite::SqliteRow;
use sqlx::*;
use ockam_core::async_trait;
use ockam_core::Result;
use ockam_node::database::{FromSqlxError, SqlxDatabase, ToSqlxType, ToVoid};
use crate::cloud::enroll::auth0::UserInfo;
use super::UsersRepository;
#[derive(Clone)]
pub struct UsersSqlxDatabase {
database: Arc<SqlxDatabase>,
}
impl UsersSqlxDatabase {
pub fn new(database: Arc<SqlxDatabase>) -> Self {
debug!("create a repository for users");
Self { database }
}
pub async fn create() -> Result<Arc<Self>> {
Ok(Arc::new(Self::new(SqlxDatabase::in_memory("users").await?)))
}
}
#[async_trait]
impl UsersRepository for UsersSqlxDatabase {
async fn store_user(&self, user: &UserInfo) -> Result<()> {
let mut transaction = self.database.begin().await.into_core()?;
let query1 =
query_scalar("SELECT EXISTS(SELECT email FROM user WHERE is_default=$1 AND email=$2)")
.bind(true.to_sql())
.bind(user.email.to_sql());
let is_already_default: bool = query1.fetch_one(&mut *transaction).await.into_core()?;
let query2 = query("INSERT OR REPLACE INTO user VALUES ($1, $2, $3, $4, $5, $6, $7, $8)")
.bind(user.email.to_sql())
.bind(user.sub.to_sql())
.bind(user.nickname.to_sql())
.bind(user.name.to_sql())
.bind(user.picture.to_sql())
.bind(user.updated_at.to_sql())
.bind(user.email_verified.to_sql())
.bind(is_already_default.to_sql());
query2.execute(&mut *transaction).await.void()?;
transaction.commit().await.void()
}
async fn get_default_user(&self) -> Result<Option<UserInfo>> {
let query = query("SELECT email FROM user WHERE is_default=$1").bind(true.to_sql());
let row: Option<SqliteRow> = query
.fetch_optional(&self.database.pool)
.await
.into_core()?;
let email: Option<String> = row.map(|r| r.get(0));
match email {
Some(email) => self.get_user(&email).await,
None => Ok(None),
}
}
async fn set_default_user(&self, email: &str) -> Result<()> {
let query = query("UPDATE user SET is_default = ? WHERE email = ?")
.bind(true.to_sql())
.bind(email.to_sql());
query.execute(&self.database.pool).await.void()
}
async fn get_user(&self, email: &str) -> Result<Option<UserInfo>> {
let query = query_as("SELECT * FROM user WHERE email=$1").bind(email.to_sql());
let row: Option<UserRow> = query
.fetch_optional(&self.database.pool)
.await
.into_core()?;
Ok(row.map(|u| u.user()))
}
async fn get_users(&self) -> Result<Vec<UserInfo>> {
let query = query_as("SELECT * FROM user");
let rows: Vec<UserRow> = query.fetch_all(&self.database.pool).await.into_core()?;
Ok(rows.iter().map(|u| u.user()).collect())
}
async fn delete_user(&self, email: &str) -> Result<()> {
let query1 = query("DELETE FROM user WHERE email=?").bind(email.to_sql());
query1.execute(&self.database.pool).await.void()
}
}
#[derive(sqlx::FromRow)]
struct UserRow {
email: String,
sub: String,
nickname: String,
name: String,
picture: String,
updated_at: String,
email_verified: bool,
#[allow(unused)]
is_default: bool,
}
impl UserRow {
fn user(&self) -> UserInfo {
UserInfo {
email: self.email.clone(),
sub: self.sub.clone(),
nickname: self.nickname.clone(),
name: self.name.clone(),
picture: self.picture.clone(),
updated_at: self.updated_at.clone(),
email_verified: self.email_verified,
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[tokio::test]
async fn test_repository() -> Result<()> {
let repository = create_repository().await?;
let user1 = UserInfo {
sub: "sub".into(),
nickname: "me".to_string(),
name: "me".to_string(),
picture: "me".to_string(),
updated_at: "today".to_string(),
email: "me@ockam.io".into(),
email_verified: false,
};
let user2 = UserInfo {
sub: "sub".into(),
nickname: "you".to_string(),
name: "you".to_string(),
picture: "you".to_string(),
updated_at: "today".to_string(),
email: "you@ockam.io".into(),
email_verified: false,
};
repository.store_user(&user1).await?;
repository.store_user(&user2).await?;
let result = repository.get_users().await?;
assert_eq!(result, vec![user1.clone(), user2.clone()]);
let result = repository.get_user("me@ockam.io").await?;
assert_eq!(result, Some(user1.clone()));
repository.set_default_user("me@ockam.io").await?;
let result = repository.get_default_user().await?;
assert_eq!(result, Some(user1.clone()));
repository.delete_user("you@ockam.io").await?;
let result = repository.get_user("you@ockam.io").await?;
assert_eq!(result, None);
let result = repository.get_users().await?;
assert_eq!(result, vec![user1.clone()]);
Ok(())
}
async fn create_repository() -> Result<Arc<dyn UsersRepository>> {
Ok(UsersSqlxDatabase::create().await?)
}
}