This repository has been archived on 2025-07-31. You can view files and clone it, but cannot push or open issues or pull requests.
Files
glyph/src/models/users.rs
Nikolaos Karaolidis ab9f2cbc09 Add fuse callbacks
Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
2025-06-07 11:00:33 +01:00

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(())
}
}