91 lines
2.5 KiB
Rust
91 lines
2.5 KiB
Rust
use std::path::Path;
|
|
use std::sync::Arc;
|
|
|
|
use serde::Deserialize;
|
|
use tracing::{info, warn};
|
|
|
|
use crate::ports::{PasswordHasher, UserRepository};
|
|
use crate::sqlite::SqliteDatabase;
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
pub struct SeedConfig {
|
|
#[serde(default)]
|
|
pub user: Option<SeedUser>,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
pub struct SeedUser {
|
|
pub email: String,
|
|
pub display_name: String,
|
|
pub password: String,
|
|
}
|
|
|
|
/// Reads the seed config file and creates the configured default user if the
|
|
/// database has no users yet. The file is optional; if it does not exist (or
|
|
/// cannot be parsed) seeding is skipped.
|
|
pub async fn seed_if_needed(
|
|
db: &SqliteDatabase,
|
|
users: &Arc<dyn UserRepository>,
|
|
hasher: &Arc<dyn PasswordHasher>,
|
|
path: &Path,
|
|
) {
|
|
let Ok(contents) = std::fs::read_to_string(path) else {
|
|
return;
|
|
};
|
|
let config: SeedConfig = match serde_json::from_str(&contents) {
|
|
Ok(config) => config,
|
|
Err(error) => {
|
|
warn!(path = %path.display(), %error, "could not parse seed config; skipping");
|
|
return;
|
|
}
|
|
};
|
|
let Some(user) = config.user else {
|
|
return;
|
|
};
|
|
if user.email.trim().is_empty() {
|
|
warn!("seed user requires a non-empty email; skipping");
|
|
return;
|
|
}
|
|
let users_for_check = users.clone();
|
|
let has_users = match db
|
|
.run(move |txn| Box::pin(async move { users_for_check.has_users(txn).await }))
|
|
.await
|
|
{
|
|
Ok(has_users) => has_users,
|
|
Err(error) => {
|
|
warn!(%error, "could not check for existing users; skipping seed");
|
|
return;
|
|
}
|
|
};
|
|
if has_users {
|
|
info!("database already has users; skipping seed");
|
|
return;
|
|
}
|
|
let password_hash = match hasher.hash(&user.password) {
|
|
Ok(hash) => hash,
|
|
Err(error) => {
|
|
warn!(%error, "could not hash seed password; skipping");
|
|
return;
|
|
}
|
|
};
|
|
let users_for_create = users.clone();
|
|
match db
|
|
.run(move |txn| {
|
|
Box::pin(async move {
|
|
users_for_create
|
|
.create_user(
|
|
txn,
|
|
user.email.trim().to_lowercase(),
|
|
user.display_name.trim().to_owned(),
|
|
password_hash,
|
|
)
|
|
.await
|
|
})
|
|
})
|
|
.await
|
|
{
|
|
Ok(user) => info!(id = user.id, email = %user.email, "seeded default user"),
|
|
Err(error) => warn!(%error, "could not seed default user"),
|
|
}
|
|
}
|