From 70591c5ac10069a4ae649bd6f79d769da9e32a98 Mon Sep 17 00:00:00 2001 From: Owen Jacobson Date: Wed, 30 Oct 2024 01:25:04 -0400 Subject: Remove `hi-recanonicalize`. This utility was needed to support a database migration with existing data. I have it on good authority that no further databases exist that are in the state that made this tool necessary. --- src/app.rs | 9 +-- src/bin/hi-recanonicalize.rs | 9 --- src/bin/hi.rs | 9 --- src/channel/app.rs | 8 -- src/channel/repo.rs | 32 -------- src/cli.rs | 170 ++++++++++++++++++++++++++++++++++++++++++ src/cli/mod.rs | 172 ------------------------------------------- src/cli/recanonicalize.rs | 86 ---------------------- src/login/app.rs | 21 ------ src/login/mod.rs | 1 + src/login/repo.rs | 32 -------- src/main.rs | 9 +++ 12 files changed, 183 insertions(+), 375 deletions(-) delete mode 100644 src/bin/hi-recanonicalize.rs delete mode 100644 src/bin/hi.rs create mode 100644 src/cli.rs delete mode 100644 src/cli/mod.rs delete mode 100644 src/cli/recanonicalize.rs create mode 100644 src/main.rs (limited to 'src') diff --git a/src/app.rs b/src/app.rs index bc1daa5..0dbf017 100644 --- a/src/app.rs +++ b/src/app.rs @@ -5,12 +5,14 @@ use crate::{ channel::app::Channels, event::{self, app::Events}, invite::app::Invites, - login::app::Logins, message::app::Messages, setup::app::Setup, token::{self, app::Tokens}, }; +#[cfg(test)] +use crate::login::app::Logins; + #[derive(Clone)] pub struct App { db: SqlitePool, @@ -47,11 +49,6 @@ impl App { Invites::new(&self.db, &self.events) } - #[cfg(not(test))] - pub const fn logins(&self) -> Logins { - Logins::new(&self.db) - } - #[cfg(test)] pub const fn logins(&self) -> Logins { Logins::new(&self.db, &self.events) diff --git a/src/bin/hi-recanonicalize.rs b/src/bin/hi-recanonicalize.rs deleted file mode 100644 index 4081276..0000000 --- a/src/bin/hi-recanonicalize.rs +++ /dev/null @@ -1,9 +0,0 @@ -use clap::Parser; - -use hi::cli; - -#[tokio::main] -async fn main() -> Result<(), cli::recanonicalize::Error> { - let args = cli::recanonicalize::Args::parse(); - args.run().await -} diff --git a/src/bin/hi.rs b/src/bin/hi.rs deleted file mode 100644 index d0830ff..0000000 --- a/src/bin/hi.rs +++ /dev/null @@ -1,9 +0,0 @@ -use clap::Parser; - -use hi::cli; - -#[tokio::main] -async fn main() -> Result<(), cli::Error> { - let args = cli::Args::parse(); - args.run().await -} diff --git a/src/channel/app.rs b/src/channel/app.rs index e32eb6c..21784e9 100644 --- a/src/channel/app.rs +++ b/src/channel/app.rs @@ -137,14 +137,6 @@ impl<'a> Channels<'a> { Ok(()) } - - pub async fn recanonicalize(&self) -> Result<(), sqlx::Error> { - let mut tx = self.db.begin().await?; - tx.channels().recanonicalize().await?; - tx.commit().await?; - - Ok(()) - } } #[derive(Debug, thiserror::Error)] diff --git a/src/channel/repo.rs b/src/channel/repo.rs index a49db52..f47e564 100644 --- a/src/channel/repo.rs +++ b/src/channel/repo.rs @@ -300,38 +300,6 @@ impl<'c> Channels<'c> { Ok(channels) } - - pub async fn recanonicalize(&mut self) -> Result<(), sqlx::Error> { - let channels = sqlx::query!( - r#" - select - id as "id: Id", - display_name as "display_name: String" - from channel_name - "#, - ) - .fetch_all(&mut *self.0) - .await?; - - for channel in channels { - let name = Name::from(channel.display_name); - let canonical_name = name.canonical(); - - sqlx::query!( - r#" - update channel_name - set canonical_name = $1 - where id = $2 - "#, - canonical_name, - channel.id, - ) - .execute(&mut *self.0) - .await?; - } - - Ok(()) - } } #[derive(Debug, thiserror::Error)] diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000..0659851 --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,170 @@ +//! The `hi` command-line interface. +//! +//! This module supports running `hi` as a freestanding program, via the +//! [`Args`] struct. + +use std::{future, io}; + +use axum::{ + http::header, + middleware, + response::{IntoResponse, Response}, + Router, +}; +use clap::{CommandFactory, Parser}; +use sqlx::sqlite::SqlitePool; +use tokio::net; + +use crate::{ + app::App, + boot, channel, clock, db, event, expire, invite, login, message, + setup::{self, middleware::setup_required}, + ui, +}; + +/// Command-line entry point for running the `hi` server. +/// +/// This is intended to be used as a Clap [Parser], to capture command-line +/// arguments for the `hi` server: +/// +/// ```no_run +/// # use hi::cli::Error; +/// # +/// # #[tokio::main] +/// # async fn main() -> Result<(), Error> { +/// use clap::Parser; +/// use hi::cli::Args; +/// +/// let args = Args::parse(); +/// args.run().await?; +/// # Ok(()) +/// # } +/// ``` +#[derive(Parser)] +#[command( + version, + about = "Run the `hi` server.", + long_about = r#"Run the `hi` server. + +The database at `--database-url` will be created, or upgraded, automatically."# +)] +pub struct Args { + /// The network address `hi` should listen on + #[arg(short, long, env, default_value = "localhost")] + address: String, + + /// The network port `hi` should listen on + #[arg(short, long, env, default_value_t = 64209)] + port: u16, + + /// Sqlite URL or path for the `hi` database + #[arg(short, long, env, default_value = "sqlite://.hi")] + database_url: String, + + /// Sqlite URL or path for a backup of the `hi` database during upgrades + #[arg(short = 'D', long, env, default_value = "sqlite://.hi.backup")] + backup_database_url: String, +} + +impl Args { + /// Runs the `hi` server, using the parsed configuation in `self`. + /// + /// This will perform the following tasks: + /// + /// * Migrate the `hi` database (at `--database-url`). + /// * Start an HTTP server (on the interface and port controlled by + /// `--address` and `--port`). + /// * Print a status message. + /// * Wait for that server to shut down. + /// + /// # Errors + /// + /// Will return `Err` if the server is unable to start, or terminates + /// prematurely. The specific [`Error`] variant will expose the cause + /// of the failure. + pub async fn run(self) -> Result<(), Error> { + let pool = self.pool().await?; + + let app = App::from(pool); + let app = routers(&app) + .route_layer(middleware::from_fn_with_state( + app.clone(), + expire::middleware, + )) + .route_layer(middleware::from_fn(clock::middleware)) + .route_layer(middleware::map_response(Self::server_info())) + .with_state(app); + + let listener = self.listener().await?; + let started_msg = started_msg(&listener)?; + + let serve = axum::serve(listener, app); + println!("{started_msg}"); + serve.await?; + + Ok(()) + } + + async fn listener(&self) -> io::Result { + let listen_addr = self.listen_addr(); + let listener = tokio::net::TcpListener::bind(listen_addr).await?; + Ok(listener) + } + + fn listen_addr(&self) -> impl net::ToSocketAddrs + '_ { + (self.address.as_str(), self.port) + } + + async fn pool(&self) -> Result { + db::prepare(&self.database_url, &self.backup_database_url).await + } + + fn server_info() -> impl Clone + Fn(Response) -> future::Ready { + let command = Self::command(); + let name = command.get_name(); + let version = command.get_version().unwrap_or("unknown version"); + let version = format!("{name}/{version}"); + move |resp| { + let response = ([(header::SERVER, &version)], resp).into_response(); + future::ready(response) + } + } +} + +fn routers(app: &App) -> Router { + [ + [ + // API endpoints that require setup to function + boot::router(), + channel::router(), + event::router(), + invite::router(), + login::router(), + message::router(), + ] + .into_iter() + .fold(Router::default(), Router::merge) + .route_layer(middleware::from_fn_with_state(app.clone(), setup_required)), + // API endpoints that handle setup + setup::router(), + // The UI (handles setup state itself) + ui::router(app), + ] + .into_iter() + .fold(Router::default(), Router::merge) +} + +fn started_msg(listener: &net::TcpListener) -> io::Result { + let local_addr = listener.local_addr()?; + Ok(format!("listening on http://{local_addr}/")) +} + +/// Errors that can be raised by [`Args::run`]. +#[derive(Debug, thiserror::Error)] +#[error(transparent)] +pub enum Error { + /// Failure due to `io::Error`. See [`io::Error`]. + Io(#[from] io::Error), + /// Failure due to a database initialization error. See [`db::Error`]. + Database(#[from] db::Error), +} diff --git a/src/cli/mod.rs b/src/cli/mod.rs deleted file mode 100644 index c75ce2b..0000000 --- a/src/cli/mod.rs +++ /dev/null @@ -1,172 +0,0 @@ -//! The `hi` command-line interface. -//! -//! This module supports running `hi` as a freestanding program, via the -//! [`Args`] struct. - -use std::{future, io}; - -use axum::{ - http::header, - middleware, - response::{IntoResponse, Response}, - Router, -}; -use clap::{CommandFactory, Parser}; -use sqlx::sqlite::SqlitePool; -use tokio::net; - -use crate::{ - app::App, - boot, channel, clock, db, event, expire, invite, login, message, - setup::{self, middleware::setup_required}, - ui, -}; - -pub mod recanonicalize; - -/// Command-line entry point for running the `hi` server. -/// -/// This is intended to be used as a Clap [Parser], to capture command-line -/// arguments for the `hi` server: -/// -/// ```no_run -/// # use hi::cli::Error; -/// # -/// # #[tokio::main] -/// # async fn main() -> Result<(), Error> { -/// use clap::Parser; -/// use hi::cli::Args; -/// -/// let args = Args::parse(); -/// args.run().await?; -/// # Ok(()) -/// # } -/// ``` -#[derive(Parser)] -#[command( - version, - about = "Run the `hi` server.", - long_about = r#"Run the `hi` server. - -The database at `--database-url` will be created, or upgraded, automatically."# -)] -pub struct Args { - /// The network address `hi` should listen on - #[arg(short, long, env, default_value = "localhost")] - address: String, - - /// The network port `hi` should listen on - #[arg(short, long, env, default_value_t = 64209)] - port: u16, - - /// Sqlite URL or path for the `hi` database - #[arg(short, long, env, default_value = "sqlite://.hi")] - database_url: String, - - /// Sqlite URL or path for a backup of the `hi` database during upgrades - #[arg(short = 'D', long, env, default_value = "sqlite://.hi.backup")] - backup_database_url: String, -} - -impl Args { - /// Runs the `hi` server, using the parsed configuation in `self`. - /// - /// This will perform the following tasks: - /// - /// * Migrate the `hi` database (at `--database-url`). - /// * Start an HTTP server (on the interface and port controlled by - /// `--address` and `--port`). - /// * Print a status message. - /// * Wait for that server to shut down. - /// - /// # Errors - /// - /// Will return `Err` if the server is unable to start, or terminates - /// prematurely. The specific [`Error`] variant will expose the cause - /// of the failure. - pub async fn run(self) -> Result<(), Error> { - let pool = self.pool().await?; - - let app = App::from(pool); - let app = routers(&app) - .route_layer(middleware::from_fn_with_state( - app.clone(), - expire::middleware, - )) - .route_layer(middleware::from_fn(clock::middleware)) - .route_layer(middleware::map_response(Self::server_info())) - .with_state(app); - - let listener = self.listener().await?; - let started_msg = started_msg(&listener)?; - - let serve = axum::serve(listener, app); - println!("{started_msg}"); - serve.await?; - - Ok(()) - } - - async fn listener(&self) -> io::Result { - let listen_addr = self.listen_addr(); - let listener = tokio::net::TcpListener::bind(listen_addr).await?; - Ok(listener) - } - - fn listen_addr(&self) -> impl net::ToSocketAddrs + '_ { - (self.address.as_str(), self.port) - } - - async fn pool(&self) -> Result { - db::prepare(&self.database_url, &self.backup_database_url).await - } - - fn server_info() -> impl Clone + Fn(Response) -> future::Ready { - let command = Self::command(); - let name = command.get_name(); - let version = command.get_version().unwrap_or("unknown version"); - let version = format!("{name}/{version}"); - move |resp| { - let response = ([(header::SERVER, &version)], resp).into_response(); - future::ready(response) - } - } -} - -fn routers(app: &App) -> Router { - [ - [ - // API endpoints that require setup to function - boot::router(), - channel::router(), - event::router(), - invite::router(), - login::router(), - message::router(), - ] - .into_iter() - .fold(Router::default(), Router::merge) - .route_layer(middleware::from_fn_with_state(app.clone(), setup_required)), - // API endpoints that handle setup - setup::router(), - // The UI (handles setup state itself) - ui::router(app), - ] - .into_iter() - .fold(Router::default(), Router::merge) -} - -fn started_msg(listener: &net::TcpListener) -> io::Result { - let local_addr = listener.local_addr()?; - Ok(format!("listening on http://{local_addr}/")) -} - -/// Errors that can be raised by [`Args::run`]. -#[derive(Debug, thiserror::Error)] -#[error(transparent)] -pub enum Error { - /// Failure due to `io::Error`. See [`io::Error`]. - Io(#[from] io::Error), - /// Failure due to a database initialization error. See [`db::Error`]. - Database(#[from] db::Error), -} diff --git a/src/cli/recanonicalize.rs b/src/cli/recanonicalize.rs deleted file mode 100644 index 9db5b77..0000000 --- a/src/cli/recanonicalize.rs +++ /dev/null @@ -1,86 +0,0 @@ -use sqlx::sqlite::SqlitePool; - -use crate::{app::App, db}; - -/// Command-line entry point for repairing canonical names in the `hi` database. -/// This command may be necessary after an upgrade, if the canonical forms of -/// names has changed. It will re-calculate the canonical form of each name in -/// the database, based on its display form, and store the results back to the -/// database. -/// -/// This is intended to be used as a Clap [Parser], to capture command-line -/// arguments for the `hi-recanonicalize` command: -/// -/// ```no_run -/// # use hi::cli::recanonicalize::Error; -/// # -/// # #[tokio::main] -/// # async fn main() -> Result<(), Error> { -/// use clap::Parser; -/// use hi::cli::recanonicalize::Args; -/// -/// let args = Args::parse(); -/// args.run().await?; -/// # Ok(()) -/// # } -/// ``` -#[derive(clap::Parser)] -#[command( - version, - about = "Recanonicalize names in the `hi` database.", - long_about = r#"Recanonicalize names in the `hi` database. - -The `hi` server must not be running while this command is run. - -The database at `--database-url` will also be created, or upgraded, automatically."# -)] -pub struct Args { - /// Sqlite URL or path for the `hi` database - #[arg(short, long, env, default_value = "sqlite://.hi")] - database_url: String, - - /// Sqlite URL or path for a backup of the `hi` database during upgrades - #[arg(short = 'D', long, env, default_value = "sqlite://.hi.backup")] - backup_database_url: String, -} - -impl Args { - /// Recanonicalizes the `hi` database, using the parsed configuation in - /// `self`. - /// - /// This will perform the following tasks: - /// - /// * Migrate the `hi` database (at `--database-url`). - /// * Recanonicalize names in the `login` and `channel` tables. - /// - /// # Errors - /// - /// Will return `Err` if the canonicalization or database upgrade processes - /// fail. The specific [`Error`] variant will expose the cause - /// of the failure. - pub async fn run(self) -> Result<(), Error> { - let pool = self.pool().await?; - - let app = App::from(pool); - app.logins().recanonicalize().await?; - app.channels().recanonicalize().await?; - - Ok(()) - } - - async fn pool(&self) -> Result { - db::prepare(&self.database_url, &self.backup_database_url).await - } -} - -/// Errors that can be raised by [`Args::run`]. -#[derive(Debug, thiserror::Error)] -#[error(transparent)] -pub enum Error { - // /// Failure due to `io::Error`. See [`io::Error`]. - // Io(#[from] io::Error), - /// Failure due to a database initialization error. See [`db::Error`]. - Database(#[from] db::Error), - /// Failure due to a data manipulation error. See [`sqlx::Error`]. - Sqlx(#[from] sqlx::Error), -} diff --git a/src/login/app.rs b/src/login/app.rs index 6da26e9..f458561 100644 --- a/src/login/app.rs +++ b/src/login/app.rs @@ -1,33 +1,21 @@ use sqlx::sqlite::SqlitePool; -use super::repo::Provider as _; - -#[cfg(test)] use super::{ create::{self, Create}, Login, Password, }; -#[cfg(test)] use crate::{clock::DateTime, event::Broadcaster, name::Name}; pub struct Logins<'a> { db: &'a SqlitePool, - #[cfg(test)] events: &'a Broadcaster, } impl<'a> Logins<'a> { - #[cfg(not(test))] - pub const fn new(db: &'a SqlitePool) -> Self { - Self { db } - } - - #[cfg(test)] pub const fn new(db: &'a SqlitePool, events: &'a Broadcaster) -> Self { Self { db, events } } - #[cfg(test)] pub async fn create( &self, name: &Name, @@ -45,17 +33,8 @@ impl<'a> Logins<'a> { Ok(login.as_created()) } - - pub async fn recanonicalize(&self) -> Result<(), sqlx::Error> { - let mut tx = self.db.begin().await?; - tx.logins().recanonicalize().await?; - tx.commit().await?; - - Ok(()) - } } -#[cfg(test)] #[derive(Debug, thiserror::Error)] pub enum CreateError { #[error("invalid login name: {0}")] diff --git a/src/login/mod.rs b/src/login/mod.rs index 5a6d715..006fa0c 100644 --- a/src/login/mod.rs +++ b/src/login/mod.rs @@ -1,3 +1,4 @@ +#[cfg(test)] pub mod app; pub mod create; pub mod event; diff --git a/src/login/repo.rs b/src/login/repo.rs index a972304..9439a25 100644 --- a/src/login/repo.rs +++ b/src/login/repo.rs @@ -143,38 +143,6 @@ impl<'c> Logins<'c> { Ok(logins) } - - pub async fn recanonicalize(&mut self) -> Result<(), sqlx::Error> { - let logins = sqlx::query!( - r#" - select - id as "id: Id", - display_name as "display_name: String" - from login - "#, - ) - .fetch_all(&mut *self.0) - .await?; - - for login in logins { - let name = Name::from(login.display_name); - let canonical_name = name.canonical(); - - sqlx::query!( - r#" - update login - set canonical_name = $1 - where id = $2 - "#, - canonical_name, - login.id, - ) - .execute(&mut *self.0) - .await?; - } - - Ok(()) - } } #[derive(Debug, thiserror::Error)] diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..d0830ff --- /dev/null +++ b/src/main.rs @@ -0,0 +1,9 @@ +use clap::Parser; + +use hi::cli; + +#[tokio::main] +async fn main() -> Result<(), cli::Error> { + let args = cli::Args::parse(); + args.run().await +} -- cgit v1.2.3