284 lines
7.5 KiB
Rust
284 lines
7.5 KiB
Rust
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<String>,
|
|
}
|
|
|
|
impl User {
|
|
pub async fn select(
|
|
pool: &PgPool,
|
|
name: &str,
|
|
) -> Result<Option<Self>, Box<dyn Error + Send + Sync>> {
|
|
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<dyn Error + Send + Sync>> {
|
|
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<dyn Error + Send + Sync>> {
|
|
query!(
|
|
r#"
|
|
DELETE FROM glyph_users
|
|
WHERE name = $1
|
|
"#,
|
|
name
|
|
)
|
|
.execute(pool)
|
|
.await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn all_exist(
|
|
pool: &PgPool,
|
|
names: &[String],
|
|
) -> Result<bool, Box<dyn Error + Send + Sync>> {
|
|
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<String>,
|
|
#[serde(default)]
|
|
pub groups: Vec<String>,
|
|
}
|
|
|
|
impl UserWithGroups {
|
|
pub async fn select_all(pool: &PgPool) -> Result<Vec<Self>, Box<dyn Error + Send + Sync>> {
|
|
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<Option<Self>, Box<dyn Error + Send + Sync>> {
|
|
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<dyn Error + Send + Sync>> {
|
|
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<dyn Error + Send + Sync>> {
|
|
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::<Vec<_>>();
|
|
|
|
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::<HashSet<_>>()
|
|
.into_iter()
|
|
.collect::<Vec<_>>();
|
|
|
|
query!(
|
|
r#"
|
|
DELETE FROM glyph_groups
|
|
WHERE name <> ALL($1)
|
|
"#,
|
|
&groups
|
|
)
|
|
.execute(pool)
|
|
.await?;
|
|
|
|
tx.commit().await?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|