diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..4085678 --- /dev/null +++ b/.env.example @@ -0,0 +1,20 @@ +# Logging (env_logger) ----------------------------------------------------------------------------------- + +RUST_LOG="rotom_discord_bot=debug" + +# Discord ------------------------------------------------------------------------------------------------ + +BOT_TOKEN="my_bot_token" + +# Database ----------------------------------------------------------------------------------------------- + +# DATABASE_DIALECT="mysql" +# DATABASE_URL="mysql://username:password@host/database" + +# DATABASE_DIALECT="postgres" +# DATABASE_URL="postgres://username:password@host/database" + +DATABASE_DIALECT="sqlite" +DATABASE_URL="/path/to/sqlite/database.db" + +# -------------------------------------------------------------------------------------------------------- diff --git a/Cargo.lock b/Cargo.lock index 70d7329..778f8eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -32,6 +32,56 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +dependencies = [ + "anstyle", + "once_cell", + "windows-sys 0.59.0", +] + [[package]] name = "async-trait" version = "0.1.85" @@ -43,6 +93,17 @@ dependencies = [ "syn", ] +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.4.0" @@ -94,7 +155,7 @@ version = "0.71.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" dependencies = [ - "bitflags", + "bitflags 2.8.0", "cexpr", "clang-sys", "itertools", @@ -106,6 +167,12 @@ dependencies = [ "syn", ] +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.8.0" @@ -185,6 +252,70 @@ dependencies = [ "libloading", ] +[[package]] +name = "clap" +version = "3.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" +dependencies = [ + "atty", + "bitflags 1.3.2", + "clap_lex 0.2.4", + "indexmap 1.9.3", + "strsim 0.10.0", + "termcolor", + "textwrap", +] + +[[package]] +name = "clap" +version = "4.5.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "769b0145982b4b48713e01ec42d61614425f27b7058bda7180a3a41f30104796" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7" +dependencies = [ + "anstream", + "anstyle", + "clap_lex 0.7.4", + "strsim 0.11.1", +] + +[[package]] +name = "clap_derive" +version = "4.5.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "clap_lex" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" + [[package]] name = "cmake" version = "0.1.52" @@ -194,6 +325,12 @@ dependencies = [ "cc", ] +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + [[package]] name = "cpufeatures" version = "0.2.16" @@ -298,7 +435,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "strsim", + "strsim 0.11.1", "syn", ] @@ -328,7 +465,7 @@ version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf1bedf64cdb9643204a36dd15b19a6ce8e7aa7f7b105868e9f1fad5ffa7d12" dependencies = [ - "bitflags", + "bitflags 2.8.0", "byteorder", "diesel_derives", "itoa", @@ -413,6 +550,15 @@ dependencies = [ "syn", ] +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +dependencies = [ + "clap 3.2.25", +] + [[package]] name = "dsl_auto_type" version = "0.1.2" @@ -433,6 +579,29 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "env_filter" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime", + "log", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -563,6 +732,12 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.15.2" @@ -580,6 +755,15 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + [[package]] name = "hmac" version = "0.12.1" @@ -589,6 +773,12 @@ dependencies = [ "digest", ] +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "icu_collections" version = "1.5.0" @@ -734,6 +924,16 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + [[package]] name = "indexmap" version = "2.7.1" @@ -741,9 +941,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.2", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "itertools" version = "0.13.0" @@ -784,7 +990,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ee7893dab2e44ae5f9d0173f26ff4aa327c10b01b06a72b52dd9405b628640d" dependencies = [ - "indexmap", + "indexmap 2.7.1", ] [[package]] @@ -847,7 +1053,7 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown", + "hashbrown 0.15.2", ] [[package]] @@ -910,7 +1116,7 @@ checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", "wasi", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -950,7 +1156,7 @@ checksum = "478b0ff3f7d67b79da2b96f56f334431aef65e15ba4b29dd74a4236e29582bdc" dependencies = [ "base64 0.21.7", "bindgen", - "bitflags", + "bitflags 2.8.0", "btoi", "byteorder", "bytes", @@ -1044,6 +1250,12 @@ version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +[[package]] +name = "os_str_bytes" +version = "6.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" + [[package]] name = "parking_lot" version = "0.12.3" @@ -1246,7 +1458,7 @@ version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ - "bitflags", + "bitflags 2.8.0", ] [[package]] @@ -1301,8 +1513,15 @@ dependencies = [ name = "rotom_discord_bot" version = "0.1.0" dependencies = [ + "clap 4.5.27", + "dotenvy", + "env_logger", + "log", "rotom_core", "rotom_database", + "secrecy", + "thiserror 2.0.11", + "tokio", ] [[package]] @@ -1344,6 +1563,15 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "secrecy" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a" +dependencies = [ + "zeroize", +] + [[package]] name = "serde" version = "1.0.217" @@ -1413,6 +1641,15 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + [[package]] name = "siphasher" version = "1.0.1" @@ -1441,7 +1678,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -1467,6 +1704,12 @@ dependencies = [ "unicode-properties", ] +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "strsim" version = "0.11.1" @@ -1511,6 +1754,21 @@ dependencies = [ "syn", ] +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" + [[package]] name = "thiserror" version = "1.0.69" @@ -1619,8 +1877,21 @@ dependencies = [ "mio", "parking_lot", "pin-project-lite", + "signal-hook-registry", "socket2", - "windows-sys", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -1689,7 +1960,7 @@ version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap", + "indexmap 2.7.1", "serde", "serde_spanned", "toml_datetime", @@ -1763,6 +2034,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "uuid" version = "1.12.0" @@ -1887,6 +2164,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -1902,6 +2188,15 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -2053,6 +2348,12 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + [[package]] name = "zerovec" version = "0.10.4" diff --git a/rotom_discord_bot/Cargo.toml b/rotom_discord_bot/Cargo.toml index 082136a..34dd80d 100644 --- a/rotom_discord_bot/Cargo.toml +++ b/rotom_discord_bot/Cargo.toml @@ -4,8 +4,15 @@ version = "0.1.0" edition = "2021" [dependencies] +clap = { version = "4.5.27", features = ["derive", "env"] } +dotenvy = { version = "0.15.7", features = ["clap"] } +env_logger = "0.11.6" +log = "0.4.25" rotom_core = { path = "../rotom_core" } rotom_database = { path = "../rotom_database", default-features = false } +secrecy = "0.10.3" +thiserror = "2.0.11" +tokio = { version = "1.43.0", features = ["full"] } [features] default = ["mysql", "postgres", "sqlite"] diff --git a/rotom_discord_bot/src/app/mod.rs b/rotom_discord_bot/src/app/mod.rs new file mode 100644 index 0000000..cac89c4 --- /dev/null +++ b/rotom_discord_bot/src/app/mod.rs @@ -0,0 +1,17 @@ +use rotom_core::repository::RepositoryProvider; + +use crate::cli::DiscordCredentials; + +#[derive(Debug, thiserror::Error)] +pub enum AppError { + +} + +pub async fn start(_credentials: DiscordCredentials, _repository_provider: R) -> Result<(), AppError> +where + R: RepositoryProvider + Send + Sync + 'static, + R::BackendError: Send + Sync, + for<'a> R::Repository<'a>: Send + Sync, +{ + todo!() +} diff --git a/rotom_discord_bot/src/cli/command.rs b/rotom_discord_bot/src/cli/command.rs new file mode 100644 index 0000000..7dbde22 --- /dev/null +++ b/rotom_discord_bot/src/cli/command.rs @@ -0,0 +1,29 @@ +use clap::Parser; + +/// Subcommand of the CLI application. +#[derive(Debug, Clone, Parser)] +pub enum Command { + /// Start the main discord bot application. + #[command( + name = "start", + about, + long_about = None, + )] + Start(super::start::Start), +} + +#[derive(Debug, thiserror::Error)] +pub enum CommandError { + #[error(transparent)] + StartError(#[from] super::start::StartError), +} + +impl Command { + pub async fn execute(self) -> Result<(), CommandError> { + match self { + Command::Start(start) => start.execute().await?, + } + + Ok(()) + } +} diff --git a/rotom_discord_bot/src/cli/mod.rs b/rotom_discord_bot/src/cli/mod.rs new file mode 100644 index 0000000..6b0cb83 --- /dev/null +++ b/rotom_discord_bot/src/cli/mod.rs @@ -0,0 +1,148 @@ +use std::fmt::Debug; +use std::path::PathBuf; + +use clap::Parser; +use clap::ValueEnum; +use command::Command; +use secrecy::SecretString; + +pub mod command; +pub mod start; + +#[derive(Debug, thiserror::Error)] +pub enum CliError { + #[error(transparent)] + Dotenvy(#[from] dotenvy::Error), +} + +/// Parses command line arguments. +pub fn parse() -> Result { + // First phase: If a dotenv file is specified, load values from it. + // This allows values in the dotenv file to be used when parsing other arguments. + let dotenv = Dotenv::parse(); + if let Some(dotenv_path) = &dotenv.path { + dotenvy::from_path_override(dotenv_path)?; + } + + // Second phase: Parse CLI options using the `Cli` parser. + let mut cli = Cli::parse(); + + // The `Cli` parser contains the `Dotenv` parser and it may parse different results in different phases. + // It is replaced here to ensure the `Cli` instance reflects the original dotenv configuration. + cli.dotenv = dotenv; + + return Ok(cli); +} + +/// Main command line interface for the librarian application. +/// +/// This struct combines the dotenv configuration with the subcommands +/// that the CLI application supports. +/// +/// The help and version flags and subcommands are explicitly enabled +/// because `Dotenv` disables them and `Cli` requires them to be enabled. +#[derive(Debug, Parser)] +#[command( + name = "rotom", + about, + version, + long_about = None, + ignore_errors = false, + disable_help_flag = false, + disable_help_subcommand = false, + disable_version_flag = false, +)] +pub struct Cli { + /// Configuration for loading environment variables from a dotenv file. + #[command(flatten)] + pub dotenv: Dotenv, + + /// The command to be executed as part of the CLI application. + #[command(subcommand)] + pub command: Command, +} + +/// Configuration for loading environment variables from a dotenv file. +/// +/// This struct is used to specify the path to a dotenv file from which +/// environment variables can be loaded. The default value is `.env`. +/// +/// The help and version flags and subcommands are disabled because +/// they are handled by `Cli` which is parsed after `Dotenv`. +#[derive(Debug, Clone, Parser)] +#[command( + ignore_errors = true, + disable_help_flag = true, + disable_help_subcommand = true, + disable_version_flag = true +)] +pub struct Dotenv { + /// The path to the dotenv file. + #[arg( + name = "path", + short = None, + long = "dotenv", + env = "DOTENV", + )] + pub path: Option, +} + +#[derive(Clone, Copy, Debug, ValueEnum)] +pub enum DatabaseDialect { + #[cfg(feature = "mysql")] + Mysql, + #[cfg(feature = "postgres")] + Postgres, + #[cfg(feature = "sqlite")] + Sqlite, +} + +impl From for rotom_database::DatabaseDialect { + fn from(value: DatabaseDialect) -> Self { + use rotom_database::DatabaseDialect as Dialect; + match value { + #[cfg(feature = "mysql")] + DatabaseDialect::Mysql => Dialect::Mysql, + #[cfg(feature = "postgres")] + DatabaseDialect::Postgres => Dialect::Postgres, + #[cfg(feature = "sqlite")] + DatabaseDialect::Sqlite => Dialect::Sqlite, + } + } +} + +/// Credentials required to establish a database connection. +#[derive(Clone, Debug, Parser)] +pub struct DatabaseCredentials { + /// The dialect of the database to connect to. + #[arg( + short = None, + long = "database-dialect", + env = "DATABASE_DIALECT", + )] + pub dialect: DatabaseDialect, + + /// The URL of the database to connect to. This should include the + /// necessary credentials (username and password) and the database + /// name, following the format: `dialect://username:password@host:port/database`. + #[arg( + short = None, + long = "database-url", + env = "DATABASE_URL", + hide_env_values(true), + )] + pub url: SecretString, +} + +/// Credentials required to authenticate a bot with Discord. +#[derive(Clone, Debug, Parser)] +pub struct DiscordCredentials { + /// The token used to authenticate the bot with Discord. + #[arg( + short = None, + long = "bot-token", + env = "BOT_TOKEN", + hide_env_values(true), + )] + pub bot_token: SecretString, +} diff --git a/rotom_discord_bot/src/cli/start.rs b/rotom_discord_bot/src/cli/start.rs new file mode 100644 index 0000000..4647b1e --- /dev/null +++ b/rotom_discord_bot/src/cli/start.rs @@ -0,0 +1,62 @@ +use clap::Parser; +use secrecy::ExposeSecret; + +use super::DatabaseCredentials; +use super::DiscordCredentials; + +/// Start the main discord bot application. +#[derive(Debug, Clone, Parser)] +pub struct Start { + /// Credentials required to establish a database connection. + #[command(flatten)] + pub database: DatabaseCredentials, + + /// Credentials required to authenticate a bot with Discord. + #[command(flatten)] + pub discord: DiscordCredentials, +} + +#[derive(Debug, thiserror::Error)] +pub enum StartError { + #[error(transparent)] + RepositoryBackendError(#[from] rotom_database::BackendError), + #[error(transparent)] + AppError(#[from] crate::app::AppError), +} + +impl Start { + pub async fn execute(self) -> Result<(), StartError> { + log::debug!("{:#?}", self); + + let database_url = self.database.url.expose_secret(); + + match self.database.dialect { + #[cfg(feature = "mysql")] + crate::cli::DatabaseDialect::Mysql => { + log::info!("Running any pending database migrations."); + rotom_database::mysql::run_pending_migrations(database_url)?; + let repository_provider = rotom_database::mysql::repository_provider(database_url).await?; + log::info!("Starting discord application."); + crate::app::start(self.discord, repository_provider).await?; + }, + #[cfg(feature = "postgres")] + crate::cli::DatabaseDialect::Postgres => { + log::info!("Running any pending database migrations."); + rotom_database::postgres::run_pending_migrations(database_url)?; + let repository_provider = rotom_database::postgres::repository_provider(database_url).await?; + log::info!("Starting discord application."); + crate::app::start(self.discord, repository_provider).await?; + }, + #[cfg(feature = "sqlite")] + crate::cli::DatabaseDialect::Sqlite => { + log::info!("Running any pending database migrations."); + rotom_database::sqlite::run_pending_migrations(database_url)?; + let repository_provider = rotom_database::sqlite::repository_provider(database_url).await?; + log::info!("Starting discord application."); + crate::app::start(self.discord, repository_provider).await?; + }, + } + + Ok(()) + } +} diff --git a/rotom_discord_bot/src/main.rs b/rotom_discord_bot/src/main.rs index f328e4d..eb4b672 100644 --- a/rotom_discord_bot/src/main.rs +++ b/rotom_discord_bot/src/main.rs @@ -1 +1,20 @@ -fn main() {} +mod app; +mod cli; + +#[derive(Debug, thiserror::Error)] +enum MainError { + #[error(transparent)] + CliError(#[from] cli::CliError), + #[error(transparent)] + CommandError(#[from] cli::command::CommandError) +} + +#[tokio::main] +async fn main() -> Result<(), MainError> { + let c = cli::parse()?; + env_logger::init(); + + c.command.execute().await?; + + Ok(()) +}