mirror of
https://github.com/CloudNebulaProject/barycenter.git
synced 2026-04-10 13:10:42 +00:00
174 lines
5.4 KiB
Rust
174 lines
5.4 KiB
Rust
use crate::storage;
|
|
use miette::{IntoDiagnostic, Result};
|
|
use sea_orm::DatabaseConnection;
|
|
use serde::{Deserialize, Serialize};
|
|
use serde_json::Value;
|
|
use std::collections::HashMap;
|
|
use std::fs;
|
|
|
|
/// User definition from JSON file
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct UserDefinition {
|
|
/// Username (unique identifier)
|
|
pub username: String,
|
|
/// User email
|
|
#[serde(default)]
|
|
pub email: Option<String>,
|
|
/// Plain text password (will be hashed)
|
|
pub password: String,
|
|
/// Whether the user account is enabled
|
|
#[serde(default = "default_true")]
|
|
pub enabled: bool,
|
|
/// Whether the email is verified
|
|
#[serde(default)]
|
|
pub email_verified: bool,
|
|
/// Custom properties to attach to the user
|
|
#[serde(default)]
|
|
pub properties: HashMap<String, Value>,
|
|
}
|
|
|
|
fn default_true() -> bool {
|
|
true
|
|
}
|
|
|
|
/// Root structure of the users JSON file
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct UsersFile {
|
|
pub users: Vec<UserDefinition>,
|
|
}
|
|
|
|
/// Sync users from a JSON file to the database (idempotent)
|
|
pub async fn sync_users_from_file(db: &DatabaseConnection, file_path: &str) -> Result<()> {
|
|
tracing::info!("Loading users from {}", file_path);
|
|
|
|
// Read and parse JSON file
|
|
let content = fs::read_to_string(file_path)
|
|
.into_diagnostic()
|
|
.map_err(|e| miette::miette!("Failed to read users file at '{}': {}", file_path, e))?;
|
|
|
|
let users_file: UsersFile = serde_json::from_str(&content)
|
|
.into_diagnostic()
|
|
.map_err(|e| {
|
|
miette::miette!(
|
|
"Failed to parse users JSON file: {}\n\nExpected format:\n{{\n \"users\": [\n {{\n \"username\": \"alice\",\n \"email\": \"alice@example.com\",\n \"password\": \"secure-password\",\n \"enabled\": true,\n \"email_verified\": false,\n \"properties\": {{\n \"department\": \"Engineering\"\n }}\n }}\n ]\n}}",
|
|
e
|
|
)
|
|
})?;
|
|
|
|
tracing::info!("Found {} user(s) in file", users_file.users.len());
|
|
|
|
let mut created = 0;
|
|
let mut updated = 0;
|
|
let mut unchanged = 0;
|
|
|
|
for user_def in users_file.users {
|
|
match sync_user(db, &user_def).await? {
|
|
SyncResult::Created => created += 1,
|
|
SyncResult::Updated => updated += 1,
|
|
SyncResult::Unchanged => unchanged += 1,
|
|
}
|
|
}
|
|
|
|
tracing::info!(
|
|
"User sync complete: {} created, {} updated, {} unchanged",
|
|
created,
|
|
updated,
|
|
unchanged
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
enum SyncResult {
|
|
Created,
|
|
Updated,
|
|
Unchanged,
|
|
}
|
|
|
|
/// Sync a single user (idempotent)
|
|
async fn sync_user(db: &DatabaseConnection, user_def: &UserDefinition) -> Result<SyncResult> {
|
|
// Check if user exists
|
|
let existing = storage::get_user_by_username(db, &user_def.username)
|
|
.await
|
|
.into_diagnostic()?;
|
|
|
|
let result = match existing {
|
|
None => {
|
|
// Create new user
|
|
tracing::info!("Creating user: {}", user_def.username);
|
|
let user = storage::create_user(
|
|
db,
|
|
&user_def.username,
|
|
&user_def.password,
|
|
user_def.email.clone(),
|
|
)
|
|
.await
|
|
.into_diagnostic()?;
|
|
|
|
// Update enabled flag if needed (email already set during creation)
|
|
if !user_def.enabled {
|
|
storage::update_user(
|
|
db,
|
|
&user.subject,
|
|
user_def.enabled,
|
|
None, // email already set
|
|
None, // requires_2fa not set during sync
|
|
)
|
|
.await
|
|
.into_diagnostic()?;
|
|
}
|
|
|
|
SyncResult::Created
|
|
}
|
|
Some(existing_user) => {
|
|
// User exists - check if update is needed
|
|
let enabled_matches = (existing_user.enabled == 1) == user_def.enabled;
|
|
let _email_verified_matches =
|
|
(existing_user.email_verified == 1) == user_def.email_verified;
|
|
let email_matches = existing_user.email == user_def.email;
|
|
|
|
if !enabled_matches || !email_matches {
|
|
tracing::info!("Updating user: {}", user_def.username);
|
|
storage::update_user(
|
|
db,
|
|
&existing_user.subject,
|
|
user_def.enabled,
|
|
if !email_matches { user_def.email.clone() } else { None },
|
|
None, // requires_2fa not set during sync
|
|
)
|
|
.await
|
|
.into_diagnostic()?;
|
|
|
|
SyncResult::Updated
|
|
} else {
|
|
SyncResult::Unchanged
|
|
}
|
|
}
|
|
};
|
|
|
|
// Sync properties
|
|
for (key, value) in &user_def.properties {
|
|
// Get the user's subject to use as owner for properties
|
|
let user = storage::get_user_by_username(db, &user_def.username)
|
|
.await
|
|
.into_diagnostic()?
|
|
.ok_or_else(|| {
|
|
miette::miette!("User not found after creation: {}", user_def.username)
|
|
})?;
|
|
|
|
storage::set_property(db, &user.subject, key, value)
|
|
.await
|
|
.into_diagnostic()?;
|
|
}
|
|
|
|
if !user_def.properties.is_empty() {
|
|
tracing::debug!(
|
|
"Synced {} properties for user {}",
|
|
user_def.properties.len(),
|
|
user_def.username
|
|
);
|
|
}
|
|
|
|
Ok(result)
|
|
}
|