Server overhaul: invite creation and CLI plumbing shared
invites::create_partner_invite is the one partner-invite path of E-12 and invite-partner; E-3's user creation moves into invited_user; invite kinds parse through rows::token; app_version_code comes from auth. cli::render_invite replaces the two identical render functions and cli::with_database the open/run/close block of four subcommands. Same output, exit codes and errors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
//! `kvitt-server` subcommands (SPEC §10.4, exhaustive), all implemented: `serve`, `migrate` and `healthcheck` here,
|
||||
//! `bootstrap` and `pair-device` (M3), `rotate-epoch` (M4) and `invite-partner` (D-113) in their own files.
|
||||
//! `kvitt-server` subcommands (SPEC §10.4, exhaustive): `serve` and `migrate` here, the others in their own files.
|
||||
|
||||
pub mod bootstrap;
|
||||
pub mod healthcheck;
|
||||
@@ -18,7 +17,9 @@ use clap::{Parser, Subcommand};
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::sync::Notify;
|
||||
|
||||
use crate::api::invites::{self, NewInvite};
|
||||
use crate::config::Config;
|
||||
use crate::db::Db;
|
||||
use crate::error::AppError;
|
||||
use crate::{AppState, db, router, tasks};
|
||||
|
||||
@@ -79,6 +80,29 @@ pub async fn run(command: Command, config: Config) -> ExitCode {
|
||||
}
|
||||
}
|
||||
|
||||
/// The text the invite subcommands print (AC-1.1, AC-1.6, D-113): the code in groups of four, the landing link
|
||||
/// (naming the inviting admin with `from`, D-087) and the expiry. The code goes to stdout only, never to the log
|
||||
/// (SPEC §8.7).
|
||||
pub fn render_invite(invite: &NewInvite, public_url: &str, from: Option<&str>) -> String {
|
||||
format!(
|
||||
"Code: {}\nLink: {}\nExpires: {}\n",
|
||||
invites::display_code(&invite.code),
|
||||
invites::invite_link(public_url, &invite.code, from),
|
||||
invite.expires_at
|
||||
)
|
||||
}
|
||||
|
||||
/// Opens the database (see [`open_database`]), runs `job` on it and closes the pool again.
|
||||
pub async fn with_database<T>(
|
||||
config: &Config,
|
||||
job: impl AsyncFnOnce(&Db) -> Result<T, AppError>,
|
||||
) -> Result<T, AppError> {
|
||||
let pool = open_database(config).await?;
|
||||
let result = job(&pool).await;
|
||||
pool.close().await;
|
||||
result
|
||||
}
|
||||
|
||||
/// Opens (creating if needed) the data directory and the database, and runs the migrations.
|
||||
pub async fn open_database(config: &Config) -> Result<db::Db, AppError> {
|
||||
std::fs::create_dir_all(&config.data_dir).map_err(|e| {
|
||||
@@ -90,8 +114,7 @@ pub async fn open_database(config: &Config) -> Result<db::Db, AppError> {
|
||||
}
|
||||
|
||||
async fn migrate(config: Config) -> Result<(), AppError> {
|
||||
let pool = open_database(&config).await?;
|
||||
pool.close().await;
|
||||
with_database(&config, async |_| Ok(())).await?;
|
||||
tracing::info!("migrations applied");
|
||||
Ok(())
|
||||
}
|
||||
@@ -100,7 +123,7 @@ async fn serve(config: Config) -> Result<(), AppError> {
|
||||
let pool = open_database(&config).await?;
|
||||
let bind = config.bind;
|
||||
// `tasks::start` installs the push queue (or logs once that push is disabled) and starts the scheduler.
|
||||
let state = tasks::start(AppState::new(config, pool.clone())).await?;
|
||||
let state = tasks::start(AppState::new(config, pool.clone()));
|
||||
|
||||
let listener = TcpListener::bind(bind)
|
||||
.await
|
||||
|
||||
Reference in New Issue
Block a user