Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
2025-06-05 14:24:48 +01:00
parent 050f25bba9
commit ed958a8ed0
41 changed files with 1885 additions and 480 deletions

View File

@@ -1,68 +1,199 @@
use non_empty_string::NonEmptyString;
use std::error::Error;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use sqlx::{FromRow, PgPool, query, query_as};
use std::{
collections::HashMap,
ops::{Deref, DerefMut},
};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
pub struct User {
pub displayname: NonEmptyString,
pub email: Option<String>,
pub password: NonEmptyString,
pub name: String,
pub display_name: String,
pub password: String,
pub email: String,
#[serde(default)]
pub disabled: bool,
pub groups: Vec<NonEmptyString>,
#[serde(default)]
pub image: Option<String>,
}
#[serde(flatten)]
pub extra: Option<HashMap<NonEmptyString, Value>>,
impl User {
pub async fn select_by_name(
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, image
FROM 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 users (name, display_name, password, email, disabled, image)
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,
image = EXCLUDED.image
"#,
user.name,
user.display_name,
user.password,
user.email,
user.disabled,
user.image
)
.execute(pool)
.await?;
Ok(())
}
pub async fn delete_by_name(
pool: &PgPool,
name: &str,
) -> Result<(), Box<dyn Error + Send + Sync>> {
query!(
r#"
DELETE FROM users
WHERE name = $1
"#,
name
)
.execute(pool)
.await?;
Ok(())
}
pub async fn all_exist_by_names(
pool: &PgPool,
names: &[String],
) -> Result<bool, Box<dyn Error + Send + Sync>> {
let row = query!(
r#"
SELECT COUNT(*) AS "count!"
FROM 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 Users {
pub users: HashMap<NonEmptyString, User>,
#[serde(flatten)]
pub extra: Option<HashMap<NonEmptyString, Value>>,
pub struct UserWithGroups {
pub name: String,
pub display_name: String,
pub password: String,
pub email: String,
#[serde(default)]
pub disabled: bool,
#[serde(default)]
pub image: Option<String>,
pub groups: Vec<String>,
}
impl Deref for Users {
type Target = HashMap<NonEmptyString, User>;
impl UserWithGroups {
pub async fn select(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.image,
COALESCE(array_agg(ug.group_name ORDER BY ug.group_name), ARRAY[]::TEXT[]) AS "groups!"
FROM users u
LEFT JOIN users_groups ug ON u.name = ug.user_name
GROUP BY u.name, u.email, u.disabled, u.image
"#
)
.fetch_all(pool)
.await?;
fn deref(&self) -> &Self::Target {
&self.users
}
}
impl DerefMut for Users {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.users
}
}
impl From<super::authelia::UserFile> for User {
fn from(user_file: super::authelia::UserFile) -> Self {
Self {
displayname: user_file.displayname,
email: user_file.email,
password: user_file.password,
disabled: user_file.disabled.unwrap_or(false),
groups: user_file.groups.unwrap_or_default(),
extra: user_file.extra,
}
}
}
impl From<super::authelia::UsersFile> for Users {
fn from(users_file: super::authelia::UsersFile) -> Self {
Self {
users: users_file
.users
.into_iter()
.map(|(key, user)| (key, User::from(user)))
.collect(),
extra: users_file.extra,
}
Ok(users)
}
pub async fn select_by_name(
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.image,
COALESCE(array_agg(ug.group_name ORDER BY ug.group_name), ARRAY[]::TEXT[]) AS "groups!"
FROM users u
LEFT JOIN users_groups ug ON u.name = ug.user_name
WHERE u.name = $1
GROUP BY u.name, u.email, u.disabled, u.image
"#,
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 users (name, display_name, password, email, disabled, image)
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.image
)
.execute(&mut *tx)
.await?;
query!(
r#"
INSERT INTO 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(())
}
}