use std::{collections::HashSet, error::Error}; use serde::{Deserialize, Serialize}; use sqlx::{FromRow, PgPool, query, query_as}; #[derive(Debug, Clone, Serialize, Deserialize, FromRow)] pub struct User { pub name: String, pub display_name: String, pub password: String, pub email: String, #[serde(default)] pub disabled: bool, #[serde(default)] pub picture: Option, } impl User { pub async fn select( pool: &PgPool, name: &str, ) -> Result, Box> { let user = query_as!( User, r#" SELECT name, display_name, password, email, disabled, picture FROM glyph_users WHERE name = $1 "#, name ) .fetch_optional(pool) .await?; Ok(user) } pub async fn upsert(pool: &PgPool, user: &Self) -> Result<(), Box> { query!( r#" INSERT INTO glyph_users (name, display_name, password, email, disabled, picture) VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT (name) DO UPDATE SET display_name = EXCLUDED.display_name, password = EXCLUDED.password, email = EXCLUDED.email, disabled = EXCLUDED.disabled, picture = EXCLUDED.picture "#, user.name, user.display_name, user.password, user.email, user.disabled, user.picture ) .execute(pool) .await?; Ok(()) } pub async fn delete(pool: &PgPool, name: &str) -> Result<(), Box> { query!( r#" DELETE FROM glyph_users WHERE name = $1 "#, name ) .execute(pool) .await?; Ok(()) } pub async fn all_exist( pool: &PgPool, names: &[String], ) -> Result> { let row = query!( r#" SELECT COUNT(*) AS "count!" FROM glyph_users WHERE name = ANY($1) "#, names ) .fetch_one(pool) .await?; Ok(row.count == i64::try_from(names.len()).unwrap()) } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct UserWithGroups { pub name: String, pub display_name: String, pub password: String, pub email: String, #[serde(default)] pub disabled: bool, #[serde(default)] pub picture: Option, #[serde(default)] pub groups: Vec, } impl UserWithGroups { pub async fn select_all(pool: &PgPool) -> Result, Box> { let users = query_as!( UserWithGroups, r#" SELECT u.name, u.display_name, u.password, u.email, u.disabled, u.picture, ARRAY(SELECT ug.group_name FROM glyph_users_groups ug WHERE ug.user_name = u.name) AS "groups!" FROM glyph_users u "# ) .fetch_all(pool) .await?; Ok(users) } pub async fn select( pool: &PgPool, name: &str, ) -> Result, Box> { let user = query_as!( UserWithGroups, r#" SELECT u.name, u.display_name, u.password, u.email, u.disabled, u.picture, ARRAY(SELECT ug.group_name FROM glyph_users_groups ug WHERE ug.user_name = u.name) AS "groups!" FROM glyph_users u WHERE u.name = $1 "#, name ) .fetch_optional(pool) .await?; Ok(user) } pub async fn insert( pool: &PgPool, user_with_groups: &Self, ) -> Result<(), Box> { let mut tx = pool.begin().await?; query!( r#" INSERT INTO glyph_users (name, display_name, password, email, disabled, picture) VALUES ($1, $2, $3, $4, $5, $6) "#, user_with_groups.name, user_with_groups.display_name, user_with_groups.password, user_with_groups.email, user_with_groups.disabled, user_with_groups.picture ) .execute(&mut *tx) .await?; query!( r#" INSERT INTO glyph_users_groups (user_name, group_name) SELECT * FROM UNNEST($1::text[], $2::text[]) "#, &user_with_groups.groups, &vec![user_with_groups.name.clone(); user_with_groups.groups.len()] ) .execute(&mut *tx) .await?; tx.commit().await?; Ok(()) } pub async fn upsert_many_delete_remaining( pool: &PgPool, users_with_groups: &[Self], ) -> Result<(), Box> { let mut tx = pool.begin().await?; for user in users_with_groups { query!( r#" INSERT INTO glyph_users (name, display_name, password, email, disabled, picture) VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT (name) DO UPDATE SET display_name = EXCLUDED.display_name, password = EXCLUDED.password, email = EXCLUDED.email, disabled = EXCLUDED.disabled, picture = EXCLUDED.picture "#, user.name, user.display_name, user.password, user.email, user.disabled, user.picture ) .execute(&mut *tx) .await?; query!( r#" DELETE FROM glyph_users_groups WHERE user_name = $1 "#, user.name ) .execute(&mut *tx) .await?; if !user.groups.is_empty() { query!( r#" INSERT INTO glyph_users_groups (user_name, group_name) SELECT * FROM UNNEST($1::text[], $2::text[]) "#, &user.groups, &vec![user.name.clone(); user.groups.len()] ) .execute(&mut *tx) .await?; } } let users = users_with_groups .iter() .map(|user| user.name.clone()) .collect::>(); query!( r#" DELETE FROM glyph_users WHERE name <> ALL($1) "#, &users ) .execute(&mut *tx) .await?; let groups = users_with_groups .iter() .flat_map(|user| user.groups.iter().cloned()) .collect::>() .into_iter() .collect::>(); query!( r#" DELETE FROM glyph_groups WHERE name <> ALL($1) "#, &groups ) .execute(pool) .await?; tx.commit().await?; Ok(()) } }