add seed ability

This commit is contained in:
2026-08-01 16:15:55 -04:00
parent 2db105bd6f
commit 5e57c61e4b
7 changed files with 90 additions and 8 deletions
+5 -7
View File
@@ -1,9 +1,10 @@
mod db;
mod hub;
mod seed;
mod views;
use std::time::Duration;
use std::{env, future::Future, sync::Arc};
use std::{env, future::Future, path::Path as FilePath, sync::Arc};
use argon2::{
Argon2,
@@ -255,6 +256,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
registration_mode,
};
let seed_path = env::var("SEED_CONFIG").unwrap_or_else(|_| "seed.json".into());
seed::seed_if_needed(&state.db, FilePath::new(&seed_path)).await;
let app = Router::new()
.route("/", get(home))
.route("/login", get(login_page).post(login))
@@ -349,12 +353,6 @@ async fn register(
form.invite.as_deref(),
)));
}
if form.password.len() < 8 {
return Ok(html_response(views::register_page(
Some("Use a password with at least 8 characters."),
form.invite.as_deref(),
)));
}
let password = form.password;
let password_hash = tokio::task::spawn_blocking(move || hash_password(&password))
+64
View File
@@ -0,0 +1,64 @@
use std::path::Path;
use serde::Deserialize;
use tracing::{info, warn};
use crate::db::Database;
#[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: &Database, 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;
}
if db.has_users().await.unwrap_or(true) {
info!("database already has users; skipping seed");
return;
}
let password_hash = match crate::hash_password(&user.password) {
Ok(hash) => hash,
Err(error) => {
warn!(%error, "could not hash seed password; skipping");
return;
}
};
match db
.create_user(
user.email.trim().to_lowercase(),
user.display_name.trim().to_owned(),
password_hash,
)
.await
{
Ok(user) => info!(id = user.id, email = %user.email, "seeded default user"),
Err(error) => warn!(%error, "could not seed default user"),
}
}
+1 -1
View File
@@ -54,7 +54,7 @@ pub fn register_page(error: Option<&str>, invite: Option<&str>) -> Markup {
label for="email" { "Email" }
input id="email" name="email" type="email" autocomplete="email" required;
label for="password" { "Password" }
input id="password" name="password" type="password" autocomplete="new-password" minlength="8" required;
input id="password" name="password" type="password" autocomplete="new-password" required;
button class="button button-primary" type="submit" { "Create account" }
}
p class="auth-switch" { "Already have an account? " a href="/login" { "Sign in" } }