From bea9a6bdc552e752d6d4ca11176bb5caf3579476 Mon Sep 17 00:00:00 2001 From: Javier Arias Date: Mon, 27 Jan 2025 11:50:56 +0000 Subject: [PATCH 01/23] Replace unwraps --- thoth-api/src/account/service.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/thoth-api/src/account/service.rs b/thoth-api/src/account/service.rs index 71d31a491..aa0dff91f 100644 --- a/thoth-api/src/account/service.rs +++ b/thoth-api/src/account/service.rs @@ -16,7 +16,7 @@ use thoth_errors::{ThothError, ThothResult}; pub fn login(user_email: &str, user_password: &str, pool: &PgPool) -> ThothResult { use crate::schema::account::dsl; - let mut conn = pool.get().unwrap(); + let mut conn = pool.get()?; let account = dsl::account .filter(dsl::email.eq(user_email)) .first::(&mut conn) @@ -32,7 +32,7 @@ pub fn login(user_email: &str, user_password: &str, pool: &PgPool) -> ThothResul pub fn get_account(email: &str, pool: &PgPool) -> ThothResult { use crate::schema::account::dsl; - let mut conn = pool.get().unwrap(); + let mut conn = pool.get()?; let account = dsl::account .filter(dsl::email.eq(email)) .first::(&mut conn) @@ -43,7 +43,7 @@ pub fn get_account(email: &str, pool: &PgPool) -> ThothResult { pub fn get_account_details(email: &str, pool: &PgPool) -> ThothResult { use crate::schema::account::dsl; - let mut conn = pool.get().unwrap(); + let mut conn = pool.get()?; let account = dsl::account .filter(dsl::email.eq(email)) .first::(&mut conn) @@ -70,7 +70,7 @@ pub fn register( ) -> ThothResult { use crate::schema; - let mut connection = pool.get().unwrap(); + let mut connection = pool.get()?; let account: NewAccount = account_data.into(); let created_account: Account = diesel::insert_into(schema::account::dsl::account) .values(&account) @@ -89,7 +89,7 @@ pub fn register( } pub fn all_emails(pool: &PgPool) -> ThothResult> { - let mut connection = pool.get().unwrap(); + let mut connection = pool.get()?; use crate::schema::account::dsl; let emails = dsl::account @@ -101,7 +101,7 @@ pub fn all_emails(pool: &PgPool) -> ThothResult> { } pub fn all_publishers(pool: &PgPool) -> ThothResult> { - let mut connection = pool.get().unwrap(); + let mut connection = pool.get()?; use crate::schema::publisher::dsl; let publishers = dsl::publisher @@ -112,7 +112,7 @@ pub fn all_publishers(pool: &PgPool) -> ThothResult> { } pub fn update_password(email: &str, password: &str, pool: &PgPool) -> ThothResult { - let mut connection = pool.get().unwrap(); + let mut connection = pool.get()?; let new_password = NewPassword::new(email.to_string(), password.to_string()); use crate::schema::account::dsl; From 82f1d2918218e3df8405e1ba4a696cfabbd5921d Mon Sep 17 00:00:00 2001 From: Javier Arias Date: Mon, 27 Jan 2025 11:55:50 +0000 Subject: [PATCH 02/23] Add account implementations --- thoth-api/src/account/handler.rs | 76 ++++++++++++++++++++++++-------- 1 file changed, 57 insertions(+), 19 deletions(-) diff --git a/thoth-api/src/account/handler.rs b/thoth-api/src/account/handler.rs index 2a7966191..dfa36608c 100644 --- a/thoth-api/src/account/handler.rs +++ b/thoth-api/src/account/handler.rs @@ -7,33 +7,52 @@ use std::time::SystemTime; use std::time::UNIX_EPOCH; use uuid::Uuid; -use crate::account::model::Account; -use crate::account::model::AccountAccess; -use crate::account::model::AccountData; -use crate::account::model::DecodedToken; -use crate::account::model::LinkedPublisher; -use crate::account::model::NewAccount; -use crate::account::model::NewPassword; -use crate::account::model::PublisherAccount; -use crate::account::model::Token; -use crate::account::service::get_account; -use crate::account::util::make_hash; -use crate::account::util::make_salt; +use crate::account::{ + model::{ + Account, AccountAccess, AccountData, DecodedToken, LinkedPublisher, NewAccount, + NewPassword, NewPublisherAccount, PublisherAccount, Token, + }, + service::get_account, + util::{make_hash, make_salt}, +}; use crate::db::PgPool; use thoth_errors::{ThothError, ThothResult}; impl Account { pub fn get_permissions(&self, pool: &PgPool) -> ThothResult> { + let publisher_accounts = self.get_publisher_accounts(pool)?; + let permissions: Vec = + publisher_accounts.into_iter().map(|p| p.into()).collect(); + Ok(permissions) + } + + pub fn get_publisher_accounts(&self, pool: &PgPool) -> ThothResult> { use crate::schema::publisher_account::dsl::*; - let mut conn = pool.get().unwrap(); + let mut conn = pool.get()?; - let linked_publishers = publisher_account + let publisher_accounts = publisher_account .filter(account_id.eq(self.account_id)) .load::(&mut conn) .expect("Error loading publisher accounts"); - let permissions: Vec = - linked_publishers.into_iter().map(|p| p.into()).collect(); - Ok(permissions) + Ok(publisher_accounts) + } + + pub fn add_publisher_account( + &self, + pool: &PgPool, + linked_publisher: LinkedPublisher, + ) -> ThothResult { + use crate::schema::publisher_account::dsl::*; + let mut conn = pool.get()?; + let new_publisher_account = NewPublisherAccount { + account_id: self.account_id, + publisher_id: linked_publisher.publisher_id, + is_admin: linked_publisher.is_admin, + }; + diesel::insert_into(publisher_account) + .values(&new_publisher_account) + .get_result::(&mut conn) + .map_err(Into::into) } pub fn get_account_access(&self, linked_publishers: Vec) -> AccountAccess { @@ -46,7 +65,7 @@ impl Account { pub fn issue_token(&self, pool: &PgPool) -> ThothResult { const DEFAULT_TOKEN_VALIDITY: i64 = 24 * 60 * 60; - let mut connection = pool.get().unwrap(); + let mut connection = pool.get()?; dotenv().ok(); let linked_publishers: Vec = self.get_permissions(pool).unwrap_or_default(); @@ -72,7 +91,7 @@ impl Account { use crate::schema::account::dsl; let updated_account = diesel::update(dsl::account.find(self.account_id)) - .set(dsl::token.eq(token.unwrap())) + .set(dsl::token.eq(token?)) .get_result::(&mut connection) .expect("Unable to set token"); Ok(updated_account.token.unwrap()) @@ -176,3 +195,22 @@ impl NewPassword { Self { email, hash, salt } } } + +impl PublisherAccount { + pub fn delete(&self, pool: &PgPool) -> ThothResult<()> { + use crate::schema::publisher_account::dsl::*; + + pool.get()?.transaction(|connection| { + diesel::delete( + publisher_account.filter( + account_id + .eq(self.account_id) + .and(publisher_id.eq(self.publisher_id)), + ), + ) + .execute(connection) + .map(|_| ()) + .map_err(Into::into) + }) + } +} From 1fec27a24f565f9bc966ca2d4ecd4c04c3621366 Mon Sep 17 00:00:00 2001 From: Javier Arias Date: Mon, 27 Jan 2025 12:41:54 +0000 Subject: [PATCH 03/23] Make commands module --- src/bin/commands/account.rs | 151 ++++++++++++++++++++++++++++++++++++ src/bin/commands/mod.rs | 8 ++ src/bin/thoth.rs | 100 ++++-------------------- 3 files changed, 173 insertions(+), 86 deletions(-) create mode 100644 src/bin/commands/account.rs create mode 100644 src/bin/commands/mod.rs diff --git a/src/bin/commands/account.rs b/src/bin/commands/account.rs new file mode 100644 index 000000000..0977399ff --- /dev/null +++ b/src/bin/commands/account.rs @@ -0,0 +1,151 @@ +use super::get_pg_pool; +use dialoguer::{console::Term, theme::ColorfulTheme, Input, MultiSelect, Password, Select}; +use std::collections::HashSet; +use thoth::api::account::{ + model::{AccountData, LinkedPublisher}, + service::{ + all_emails, all_publishers, get_account, register as register_account, update_password, + }, +}; +use thoth_errors::ThothResult; + +pub fn register(arguments: &clap::ArgMatches) -> ThothResult<()> { + let pool = get_pg_pool(arguments); + + let name = Input::new() + .with_prompt("Enter given name") + .interact_on(&Term::stdout())?; + let surname = Input::new() + .with_prompt("Enter family name") + .interact_on(&Term::stdout())?; + let email = Input::new() + .with_prompt("Enter email address") + .interact_on(&Term::stdout())?; + let password = Password::new() + .with_prompt("Enter password") + .with_confirmation("Confirm password", "Passwords do not match") + .interact_on(&Term::stdout())?; + let is_superuser: bool = Input::new() + .with_prompt("Is this a superuser account") + .default(false) + .interact_on(&Term::stdout())?; + let is_bot: bool = Input::new() + .with_prompt("Is this a bot account") + .default(false) + .interact_on(&Term::stdout())?; + + let mut linked_publishers = vec![]; + if let Ok(publishers) = all_publishers(&pool) { + let chosen: Vec = MultiSelect::new() + .items(&publishers) + .with_prompt("Select publishers to link this account to") + .interact_on(&Term::stdout())?; + for index in chosen { + let publisher = publishers.get(index).unwrap(); + let is_admin: bool = Input::new() + .with_prompt(format!( + "Make user an admin of '{}'?", + publisher.publisher_name + )) + .default(false) + .interact_on(&Term::stdout())?; + let linked_publisher = LinkedPublisher { + publisher_id: publisher.publisher_id, + is_admin, + }; + linked_publishers.push(linked_publisher); + } + } + let account_data = AccountData { + name, + surname, + email, + password, + is_superuser, + is_bot, + }; + register_account(account_data, linked_publishers, &pool).map(|_| ()) +} + +pub fn publisher(arguments: &clap::ArgMatches) -> ThothResult<()> { + let pool = get_pg_pool(arguments); + + let all_emails = all_emails(&pool).expect("No user accounts present in database."); + let email_selection = Select::with_theme(&ColorfulTheme::default()) + .items(&all_emails) + .default(0) + .with_prompt("Select a user account") + .interact_on(&Term::stdout())?; + let email = all_emails.get(email_selection).unwrap(); + let account = get_account(email, &pool)?; + + let publishers = all_publishers(&pool)?; + let publisher_accounts = account.get_publisher_accounts(&pool)?; + let items_checked: Vec<(_, bool)> = publishers + .iter() + .map(|publisher| { + let is_linked = publisher_accounts + .iter() + .any(|pa| pa.publisher_id == publisher.publisher_id); + (publisher, is_linked) + }) + .collect(); + + let chosen: Vec = MultiSelect::new() + .with_prompt("Select publishers to link this account to") + .items_checked(&items_checked) + .interact_on(&Term::stdout())?; + let chosen_ids: HashSet<_> = chosen + .iter() + .map(|&index| items_checked[index].0.publisher_id) + .collect(); + let current_ids: HashSet<_> = publisher_accounts + .iter() + .map(|pa| pa.publisher_id) + .collect(); + let to_add: Vec<_> = publishers + .iter() + .filter(|p| chosen_ids.contains(&p.publisher_id) && !current_ids.contains(&p.publisher_id)) + .collect(); + let to_remove: Vec<_> = publisher_accounts + .iter() + .filter(|pa| !chosen_ids.contains(&pa.publisher_id)) + .collect(); + + for publisher in to_add { + let is_admin: bool = Input::new() + .with_prompt(format!( + "Make user an admin of '{}'?", + publisher.publisher_name + )) + .default(false) + .interact_on(&Term::stdout())?; + let linked_publisher = LinkedPublisher { + publisher_id: publisher.publisher_id, + is_admin, + }; + account.add_publisher_account(&pool, linked_publisher)?; + } + for publisher_account in to_remove { + publisher_account.delete(&pool)?; + } + + Ok(()) +} + +pub fn password(arguments: &clap::ArgMatches) -> ThothResult<()> { + let pool = get_pg_pool(arguments); + let all_emails = all_emails(&pool).expect("No user accounts present in database."); + let email_selection = Select::with_theme(&ColorfulTheme::default()) + .items(&all_emails) + .default(0) + .with_prompt("Select a user account") + .interact_on(&Term::stdout())?; + let password = Password::new() + .with_prompt("Enter new password") + .with_confirmation("Confirm password", "Passwords do not match") + .interact_on(&Term::stdout())?; + let email = all_emails.get(email_selection).unwrap(); + + update_password(email, &password, &pool).map(|_| ()) +} diff --git a/src/bin/commands/mod.rs b/src/bin/commands/mod.rs new file mode 100644 index 000000000..476f3ba9a --- /dev/null +++ b/src/bin/commands/mod.rs @@ -0,0 +1,8 @@ +use thoth_api::db::{init_pool as init_pg_pool, PgPool}; + +pub(crate) mod account; + +fn get_pg_pool(arguments: &clap::ArgMatches) -> PgPool { + let database_url = arguments.get_one::("db").unwrap(); + init_pg_pool(database_url) +} diff --git a/src/bin/thoth.rs b/src/bin/thoth.rs index 78b560d58..452356a96 100644 --- a/src/bin/thoth.rs +++ b/src/bin/thoth.rs @@ -1,14 +1,10 @@ use clap::{crate_authors, crate_version, value_parser, Arg, ArgAction, Command}; -use dialoguer::{console::Term, theme::ColorfulTheme, Input, MultiSelect, Password, Select}; +use dialoguer::{console::Term, MultiSelect}; use dotenv::dotenv; use std::env; use thoth::{ api::{ - account::{ - model::{AccountData, LinkedPublisher}, - service::{all_emails, all_publishers, register, update_password}, - }, - db::{init_pool as init_pg_pool, revert_migrations, run_migrations}, + db::{revert_migrations, run_migrations}, redis::{del, init_pool as init_redis_pool, scan_match}, }, api_server, app_server, @@ -16,6 +12,8 @@ use thoth::{ export_server, ALL_SPECIFICATIONS, }; +mod commands; + fn database_argument() -> Arg { Arg::new("db") .short('D') @@ -224,6 +222,10 @@ fn thoth_commands() -> Command { .subcommand_required(true) .arg_required_else_help(true) .subcommand(Command::new("register").about("Create a new user account")) + .subcommand( + Command::new("publisher") + .about("Select which publisher(s) this account can manage"), + ) .subcommand(Command::new("password").about("Reset a password")), ) .subcommand( @@ -336,86 +338,12 @@ fn main() -> ThothResult<()> { ) .map_err(|e| e.into()) } - Some(("account", account_matches)) => { - let database_url = account_matches.get_one::("db").unwrap(); - match account_matches.subcommand() { - Some(("register", _)) => { - let pool = init_pg_pool(database_url); - - let name = Input::new() - .with_prompt("Enter given name") - .interact_on(&Term::stdout())?; - let surname = Input::new() - .with_prompt("Enter family name") - .interact_on(&Term::stdout())?; - let email = Input::new() - .with_prompt("Enter email address") - .interact_on(&Term::stdout())?; - let password = Password::new() - .with_prompt("Enter password") - .with_confirmation("Confirm password", "Passwords do not match") - .interact_on(&Term::stdout())?; - let is_superuser: bool = Input::new() - .with_prompt("Is this a superuser account") - .default(false) - .interact_on(&Term::stdout())?; - let is_bot: bool = Input::new() - .with_prompt("Is this a bot account") - .default(false) - .interact_on(&Term::stdout())?; - - let mut linked_publishers = vec![]; - if let Ok(publishers) = all_publishers(&pool) { - let chosen: Vec = MultiSelect::new() - .items(&publishers) - .with_prompt("Select publishers to link this account to") - .interact_on(&Term::stdout())?; - for index in chosen { - let publisher = publishers.get(index).unwrap(); - let is_admin: bool = Input::new() - .with_prompt(format!( - "Make user an admin of '{}'?", - publisher.publisher_name - )) - .default(false) - .interact_on(&Term::stdout())?; - let linked_publisher = LinkedPublisher { - publisher_id: publisher.publisher_id, - is_admin, - }; - linked_publishers.push(linked_publisher); - } - } - let account_data = AccountData { - name, - surname, - email, - password, - is_superuser, - is_bot, - }; - register(account_data, linked_publishers, &pool).map(|_| ()) - } - Some(("password", _)) => { - let pool = init_pg_pool(database_url); - let all_emails = - all_emails(&pool).expect("No user accounts present in database."); - let email_selection = Select::with_theme(&ColorfulTheme::default()) - .items(&all_emails) - .default(0) - .with_prompt("Select a user account") - .interact_on(&Term::stdout())?; - let password = Password::new() - .with_prompt("Enter new password") - .with_confirmation("Confirm password", "Passwords do not match") - .interact_on(&Term::stdout())?; - let email = all_emails.get(email_selection).unwrap(); - - update_password(email, &password, &pool).map(|_| ()) - } - _ => unreachable!(), - } - } + Some(("account", account_matches)) => match account_matches.subcommand() { + Some(("register", _)) => commands::account::register(account_matches), + Some(("publisher", _)) => commands::account::publisher(account_matches), + Some(("password", _)) => commands::account::password(account_matches), + _ => unreachable!(), + }, Some(("cache", cache_matches)) => match cache_matches.subcommand() { Some(("delete", _)) => { let redis_url = cache_matches.get_one::("redis").unwrap(); From 25a2b953277a2f21a1ea7c6c16b9130489b1cbe4 Mon Sep 17 00:00:00 2001 From: Javier Arias Date: Mon, 27 Jan 2025 12:43:52 +0000 Subject: [PATCH 04/23] Update changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index efc153707..e63b4ef0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Changed + - New binary submodule, `commands` + +### Added + - CLI subcommand `thoth account publishers` to modify which publisher(s) an account has access to ## [[0.13.5]](https://github.com/thoth-pub/thoth/releases/tag/v0.13.5) - 2025-01-17 ### Changed From d1e0fb1d907386462d436a346b50ab05bb3f7f3e Mon Sep 17 00:00:00 2001 From: Javier Arias Date: Mon, 27 Jan 2025 13:34:26 +0000 Subject: [PATCH 05/23] Abstract account command --- src/bin/commands/account.rs | 85 +++++++++++++++++--------------- src/bin/thoth.rs | 4 +- thoth-api/src/account/service.rs | 16 +----- 3 files changed, 47 insertions(+), 58 deletions(-) diff --git a/src/bin/commands/account.rs b/src/bin/commands/account.rs index 0977399ff..cd9cae6eb 100644 --- a/src/bin/commands/account.rs +++ b/src/bin/commands/account.rs @@ -7,7 +7,37 @@ use thoth::api::account::{ all_emails, all_publishers, get_account, register as register_account, update_password, }, }; -use thoth_errors::ThothResult; +use thoth_api::db::PgPool; +use thoth_errors::{ThothError, ThothResult}; + +fn email_selection(pool: &PgPool) -> ThothResult { + let all_emails = all_emails(&pool).expect("No user accounts present in database."); + let email_selection = Select::with_theme(&ColorfulTheme::default()) + .items(&all_emails) + .default(0) + .with_prompt("Select a user account") + .interact_on(&Term::stdout())?; + all_emails + .get(email_selection) + .cloned() + .ok_or_else(|| ThothError::InternalError("Invalid user selection".into())) +} + +fn password_input() -> ThothResult { + Password::new() + .with_prompt("Enter password") + .with_confirmation("Confirm password", "Passwords do not match") + .interact_on(&Term::stdout()) + .map_err(Into::into) +} + +fn is_admin_input(publisher_name: &str) -> ThothResult { + Input::new() + .with_prompt(format!("Make user an admin of '{}'?", publisher_name)) + .default(false) + .interact_on(&Term::stdout()) + .map_err(Into::into) +} pub fn register(arguments: &clap::ArgMatches) -> ThothResult<()> { let pool = get_pg_pool(arguments); @@ -21,10 +51,7 @@ pub fn register(arguments: &clap::ArgMatches) -> ThothResult<()> { let email = Input::new() .with_prompt("Enter email address") .interact_on(&Term::stdout())?; - let password = Password::new() - .with_prompt("Enter password") - .with_confirmation("Confirm password", "Passwords do not match") - .interact_on(&Term::stdout())?; + let password = password_input()?; let is_superuser: bool = Input::new() .with_prompt("Is this a superuser account") .default(false) @@ -42,13 +69,7 @@ pub fn register(arguments: &clap::ArgMatches) -> ThothResult<()> { .interact_on(&Term::stdout())?; for index in chosen { let publisher = publishers.get(index).unwrap(); - let is_admin: bool = Input::new() - .with_prompt(format!( - "Make user an admin of '{}'?", - publisher.publisher_name - )) - .default(false) - .interact_on(&Term::stdout())?; + let is_admin: bool = is_admin_input(&publisher.publisher_name)?; let linked_publisher = LinkedPublisher { publisher_id: publisher.publisher_id, is_admin, @@ -64,20 +85,17 @@ pub fn register(arguments: &clap::ArgMatches) -> ThothResult<()> { is_superuser, is_bot, }; - register_account(account_data, linked_publishers, &pool).map(|_| ()) + let account = register_account(&pool, account_data)?; + for linked_publisher in linked_publishers { + account.add_publisher_account(&pool, linked_publisher)?; + } + Ok(()) } -pub fn publisher(arguments: &clap::ArgMatches) -> ThothResult<()> { +pub fn publishers(arguments: &clap::ArgMatches) -> ThothResult<()> { let pool = get_pg_pool(arguments); - let all_emails = all_emails(&pool).expect("No user accounts present in database."); - let email_selection = Select::with_theme(&ColorfulTheme::default()) - .items(&all_emails) - .default(0) - .with_prompt("Select a user account") - .interact_on(&Term::stdout())?; - let email = all_emails.get(email_selection).unwrap(); - let account = get_account(email, &pool)?; + let account = email_selection(&pool).and_then(|email| get_account(&email, &pool))?; let publishers = all_publishers(&pool)?; let publisher_accounts = account.get_publisher_accounts(&pool)?; @@ -113,13 +131,7 @@ pub fn publisher(arguments: &clap::ArgMatches) -> ThothResult<()> { .collect(); for publisher in to_add { - let is_admin: bool = Input::new() - .with_prompt(format!( - "Make user an admin of '{}'?", - publisher.publisher_name - )) - .default(false) - .interact_on(&Term::stdout())?; + let is_admin: bool = is_admin_input(&publisher.publisher_name)?; let linked_publisher = LinkedPublisher { publisher_id: publisher.publisher_id, is_admin, @@ -135,17 +147,8 @@ pub fn publisher(arguments: &clap::ArgMatches) -> ThothResult<()> { pub fn password(arguments: &clap::ArgMatches) -> ThothResult<()> { let pool = get_pg_pool(arguments); - let all_emails = all_emails(&pool).expect("No user accounts present in database."); - let email_selection = Select::with_theme(&ColorfulTheme::default()) - .items(&all_emails) - .default(0) - .with_prompt("Select a user account") - .interact_on(&Term::stdout())?; - let password = Password::new() - .with_prompt("Enter new password") - .with_confirmation("Confirm password", "Passwords do not match") - .interact_on(&Term::stdout())?; - let email = all_emails.get(email_selection).unwrap(); + let email = email_selection(&pool)?; + let password = password_input()?; - update_password(email, &password, &pool).map(|_| ()) + update_password(&email, &password, &pool).map(|_| ()) } diff --git a/src/bin/thoth.rs b/src/bin/thoth.rs index 452356a96..8c19dbd70 100644 --- a/src/bin/thoth.rs +++ b/src/bin/thoth.rs @@ -223,7 +223,7 @@ fn thoth_commands() -> Command { .arg_required_else_help(true) .subcommand(Command::new("register").about("Create a new user account")) .subcommand( - Command::new("publisher") + Command::new("publishers") .about("Select which publisher(s) this account can manage"), ) .subcommand(Command::new("password").about("Reset a password")), @@ -340,7 +340,7 @@ fn main() -> ThothResult<()> { } Some(("account", account_matches)) => match account_matches.subcommand() { Some(("register", _)) => commands::account::register(account_matches), - Some(("publisher", _)) => commands::account::publisher(account_matches), + Some(("publishers", _)) => commands::account::publishers(account_matches), Some(("password", _)) => commands::account::password(account_matches), _ => unreachable!(), }, diff --git a/thoth-api/src/account/service.rs b/thoth-api/src/account/service.rs index aa0dff91f..ee0b8ba6a 100644 --- a/thoth-api/src/account/service.rs +++ b/thoth-api/src/account/service.rs @@ -63,11 +63,7 @@ pub fn get_account_details(email: &str, pool: &PgPool) -> ThothResult, - pool: &PgPool, -) -> ThothResult { +pub fn register(pool: &PgPool, account_data: AccountData) -> ThothResult { use crate::schema; let mut connection = pool.get()?; @@ -75,16 +71,6 @@ pub fn register( let created_account: Account = diesel::insert_into(schema::account::dsl::account) .values(&account) .get_result::(&mut connection)?; - for linked_publisher in linked_publishers { - let publisher_account = NewPublisherAccount { - account_id: created_account.account_id, - publisher_id: linked_publisher.publisher_id, - is_admin: linked_publisher.is_admin, - }; - diesel::insert_into(schema::publisher_account::dsl::publisher_account) - .values(&publisher_account) - .get_result::(&mut connection)?; - } Ok(created_account) } From 6b8c62fb16f37d989a945e36bc2ff5011bf1e9bb Mon Sep 17 00:00:00 2001 From: Javier Arias Date: Mon, 27 Jan 2025 13:50:15 +0000 Subject: [PATCH 06/23] Abstract account registration --- src/bin/commands/account.rs | 120 +++++++++++++------------------ thoth-api/src/account/service.rs | 25 +++++-- 2 files changed, 69 insertions(+), 76 deletions(-) diff --git a/src/bin/commands/account.rs b/src/bin/commands/account.rs index cd9cae6eb..16b8e26fe 100644 --- a/src/bin/commands/account.rs +++ b/src/bin/commands/account.rs @@ -1,15 +1,57 @@ use super::get_pg_pool; use dialoguer::{console::Term, theme::ColorfulTheme, Input, MultiSelect, Password, Select}; use std::collections::HashSet; -use thoth::api::account::{ - model::{AccountData, LinkedPublisher}, - service::{ - all_emails, all_publishers, get_account, register as register_account, update_password, +use thoth::api::{ + account::{ + model::{Account, AccountData, LinkedPublisher}, + service::{ + all_emails, all_publishers, get_account, register as register_account, update_password, + }, }, + db::PgPool, }; -use thoth_api::db::PgPool; use thoth_errors::{ThothError, ThothResult}; +pub fn register(arguments: &clap::ArgMatches) -> ThothResult<()> { + let pool = get_pg_pool(arguments); + + let name = Input::new() + .with_prompt("Enter given name") + .interact_on(&Term::stdout())?; + let surname = Input::new() + .with_prompt("Enter family name") + .interact_on(&Term::stdout())?; + let email = Input::new() + .with_prompt("Enter email address") + .interact_on(&Term::stdout())?; + let password = password_input()?; + let is_superuser: bool = Input::new() + .with_prompt("Is this a superuser account") + .default(false) + .interact_on(&Term::stdout())?; + let is_bot: bool = Input::new() + .with_prompt("Is this a bot account") + .default(false) + .interact_on(&Term::stdout())?; + + let account = register_account(&pool, name, surname, email, password, is_superuser, is_bot)?; + select_and_link_publishers(&pool, &account) +} + +pub fn publishers(arguments: &clap::ArgMatches) -> ThothResult<()> { + let pool = get_pg_pool(arguments); + let account = email_selection(&pool).and_then(|email| get_account(&email, &pool))?; + select_and_link_publishers(&pool, &account) +} + +pub fn password(arguments: &clap::ArgMatches) -> ThothResult<()> { + let pool = get_pg_pool(arguments); + let email = email_selection(&pool)?; + let password = password_input()?; + + update_password(&email, &password, &pool).map(|_| ()) +} + fn email_selection(pool: &PgPool) -> ThothResult { let all_emails = all_emails(&pool).expect("No user accounts present in database."); let email_selection = Select::with_theme(&ColorfulTheme::default()) @@ -39,64 +81,7 @@ fn is_admin_input(publisher_name: &str) -> ThothResult { .map_err(Into::into) } -pub fn register(arguments: &clap::ArgMatches) -> ThothResult<()> { - let pool = get_pg_pool(arguments); - - let name = Input::new() - .with_prompt("Enter given name") - .interact_on(&Term::stdout())?; - let surname = Input::new() - .with_prompt("Enter family name") - .interact_on(&Term::stdout())?; - let email = Input::new() - .with_prompt("Enter email address") - .interact_on(&Term::stdout())?; - let password = password_input()?; - let is_superuser: bool = Input::new() - .with_prompt("Is this a superuser account") - .default(false) - .interact_on(&Term::stdout())?; - let is_bot: bool = Input::new() - .with_prompt("Is this a bot account") - .default(false) - .interact_on(&Term::stdout())?; - - let mut linked_publishers = vec![]; - if let Ok(publishers) = all_publishers(&pool) { - let chosen: Vec = MultiSelect::new() - .items(&publishers) - .with_prompt("Select publishers to link this account to") - .interact_on(&Term::stdout())?; - for index in chosen { - let publisher = publishers.get(index).unwrap(); - let is_admin: bool = is_admin_input(&publisher.publisher_name)?; - let linked_publisher = LinkedPublisher { - publisher_id: publisher.publisher_id, - is_admin, - }; - linked_publishers.push(linked_publisher); - } - } - let account_data = AccountData { - name, - surname, - email, - password, - is_superuser, - is_bot, - }; - let account = register_account(&pool, account_data)?; - for linked_publisher in linked_publishers { - account.add_publisher_account(&pool, linked_publisher)?; - } - Ok(()) -} - -pub fn publishers(arguments: &clap::ArgMatches) -> ThothResult<()> { - let pool = get_pg_pool(arguments); - - let account = email_selection(&pool).and_then(|email| get_account(&email, &pool))?; - +fn select_and_link_publishers(pool: &PgPool, account: &Account) -> ThothResult<()> { let publishers = all_publishers(&pool)?; let publisher_accounts = account.get_publisher_accounts(&pool)?; let items_checked: Vec<(_, bool)> = publishers @@ -141,14 +126,5 @@ pub fn publishers(arguments: &clap::ArgMatches) -> ThothResult<()> { for publisher_account in to_remove { publisher_account.delete(&pool)?; } - Ok(()) } - -pub fn password(arguments: &clap::ArgMatches) -> ThothResult<()> { - let pool = get_pg_pool(arguments); - let email = email_selection(&pool)?; - let password = password_input()?; - - update_password(&email, &password, &pool).map(|_| ()) -} diff --git a/thoth-api/src/account/service.rs b/thoth-api/src/account/service.rs index ee0b8ba6a..36800dac5 100644 --- a/thoth-api/src/account/service.rs +++ b/thoth-api/src/account/service.rs @@ -11,6 +11,7 @@ use crate::account::model::PublisherAccount; use crate::account::util::verify; use crate::db::PgPool; use crate::model::publisher::Publisher; +use crate::schema::account::is_superuser; use thoth_errors::{ThothError, ThothResult}; pub fn login(user_email: &str, user_password: &str, pool: &PgPool) -> ThothResult { @@ -63,12 +64,28 @@ pub fn get_account_details(email: &str, pool: &PgPool) -> ThothResult ThothResult { - use crate::schema; +pub fn register( + pool: &PgPool, + name: String, + surname: String, + email: String, + password: String, + is_superuser: bool, + is_bot: bool, +) -> ThothResult { + use crate::schema::account::dsl; let mut connection = pool.get()?; - let account: NewAccount = account_data.into(); - let created_account: Account = diesel::insert_into(schema::account::dsl::account) + let account: NewAccount = AccountData { + name, + surname, + email, + password, + is_superuser, + is_bot, + } + .into(); + let created_account: Account = diesel::insert_into(dsl::account) .values(&account) .get_result::(&mut connection)?; Ok(created_account) From 8123e59a7bcc4094c4cdd63aa64ef831988fc5a3 Mon Sep 17 00:00:00 2001 From: Javier Arias Date: Mon, 27 Jan 2025 14:04:24 +0000 Subject: [PATCH 07/23] Abstract publisher accounts --- src/bin/commands/account.rs | 28 ++++++++++++---------------- thoth-api/src/account/service.rs | 14 ++++---------- 2 files changed, 16 insertions(+), 26 deletions(-) diff --git a/src/bin/commands/account.rs b/src/bin/commands/account.rs index 16b8e26fe..3c51ba023 100644 --- a/src/bin/commands/account.rs +++ b/src/bin/commands/account.rs @@ -3,7 +3,7 @@ use dialoguer::{console::Term, theme::ColorfulTheme, Input, MultiSelect, Passwor use std::collections::HashSet; use thoth::api::{ account::{ - model::{Account, AccountData, LinkedPublisher}, + model::{Account, LinkedPublisher}, service::{ all_emails, all_publishers, get_account, register as register_account, update_password, }, @@ -53,7 +53,7 @@ pub fn password(arguments: &clap::ArgMatches) -> ThothResult<()> { } fn email_selection(pool: &PgPool) -> ThothResult { - let all_emails = all_emails(&pool).expect("No user accounts present in database."); + let all_emails = all_emails(pool).expect("No user accounts present in database."); let email_selection = Select::with_theme(&ColorfulTheme::default()) .items(&all_emails) .default(0) @@ -82,16 +82,16 @@ fn is_admin_input(publisher_name: &str) -> ThothResult { } fn select_and_link_publishers(pool: &PgPool, account: &Account) -> ThothResult<()> { - let publishers = all_publishers(&pool)?; - let publisher_accounts = account.get_publisher_accounts(&pool)?; + let publishers = all_publishers(pool)?; + let publisher_accounts = account.get_publisher_accounts(pool)?; + let current_ids: HashSet<_> = publisher_accounts + .iter() + .map(|pa| pa.publisher_id) + .collect(); + let items_checked: Vec<(_, bool)> = publishers .iter() - .map(|publisher| { - let is_linked = publisher_accounts - .iter() - .any(|pa| pa.publisher_id == publisher.publisher_id); - (publisher, is_linked) - }) + .map(|publisher| (publisher, current_ids.contains(&publisher.publisher_id))) .collect(); let chosen: Vec = MultiSelect::new() @@ -102,10 +102,6 @@ fn select_and_link_publishers(pool: &PgPool, account: &Account) -> ThothResult<( .iter() .map(|&index| items_checked[index].0.publisher_id) .collect(); - let current_ids: HashSet<_> = publisher_accounts - .iter() - .map(|pa| pa.publisher_id) - .collect(); let to_add: Vec<_> = publishers .iter() .filter(|p| chosen_ids.contains(&p.publisher_id) && !current_ids.contains(&p.publisher_id)) @@ -121,10 +117,10 @@ fn select_and_link_publishers(pool: &PgPool, account: &Account) -> ThothResult<( publisher_id: publisher.publisher_id, is_admin, }; - account.add_publisher_account(&pool, linked_publisher)?; + account.add_publisher_account(pool, linked_publisher)?; } for publisher_account in to_remove { - publisher_account.delete(&pool)?; + publisher_account.delete(pool)?; } Ok(()) } diff --git a/thoth-api/src/account/service.rs b/thoth-api/src/account/service.rs index 36800dac5..5333f0155 100644 --- a/thoth-api/src/account/service.rs +++ b/thoth-api/src/account/service.rs @@ -1,17 +1,11 @@ use diesel::prelude::*; -use crate::account::model::Account; -use crate::account::model::AccountData; -use crate::account::model::AccountDetails; -use crate::account::model::LinkedPublisher; -use crate::account::model::NewAccount; -use crate::account::model::NewPassword; -use crate::account::model::NewPublisherAccount; -use crate::account::model::PublisherAccount; -use crate::account::util::verify; +use crate::account::{ + model::{Account, AccountData, AccountDetails, LinkedPublisher, NewAccount, NewPassword}, + util::verify, +}; use crate::db::PgPool; use crate::model::publisher::Publisher; -use crate::schema::account::is_superuser; use thoth_errors::{ThothError, ThothResult}; pub fn login(user_email: &str, user_password: &str, pool: &PgPool) -> ThothResult { From a27b890fb53e3f5c51c08e057d204118d312ecd0 Mon Sep 17 00:00:00 2001 From: Javier Arias Date: Mon, 27 Jan 2025 14:16:36 +0000 Subject: [PATCH 08/23] Abstract cache command --- src/bin/commands/account.rs | 17 ++++++++++------- src/bin/commands/cache.rs | 31 +++++++++++++++++++++++++++++++ src/bin/commands/mod.rs | 13 +++++++++++-- src/bin/thoth.rs | 34 ++++------------------------------ 4 files changed, 56 insertions(+), 39 deletions(-) create mode 100644 src/bin/commands/cache.rs diff --git a/src/bin/commands/account.rs b/src/bin/commands/account.rs index 3c51ba023..5faba8642 100644 --- a/src/bin/commands/account.rs +++ b/src/bin/commands/account.rs @@ -1,16 +1,19 @@ use super::get_pg_pool; use dialoguer::{console::Term, theme::ColorfulTheme, Input, MultiSelect, Password, Select}; use std::collections::HashSet; -use thoth::api::{ - account::{ - model::{Account, LinkedPublisher}, - service::{ - all_emails, all_publishers, get_account, register as register_account, update_password, +use thoth::{ + api::{ + account::{ + model::{Account, LinkedPublisher}, + service::{ + all_emails, all_publishers, get_account, register as register_account, + update_password, + }, }, + db::PgPool, }, - db::PgPool, + errors::{ThothError, ThothResult}, }; -use thoth_errors::{ThothError, ThothResult}; pub fn register(arguments: &clap::ArgMatches) -> ThothResult<()> { let pool = get_pg_pool(arguments); diff --git a/src/bin/commands/cache.rs b/src/bin/commands/cache.rs new file mode 100644 index 000000000..1cd56021a --- /dev/null +++ b/src/bin/commands/cache.rs @@ -0,0 +1,31 @@ +use crate::commands::get_redis_pool; +use clap::ArgMatches; +use dialoguer::{console::Term, MultiSelect}; +use thoth::{ + api::redis::{del, scan_match}, + errors::{ThothError, ThothResult}, + ALL_SPECIFICATIONS, +}; + +pub fn delete(arguments: &ArgMatches) -> ThothResult<()> { + let pool = get_redis_pool(arguments); + let chosen: Vec = MultiSelect::new() + .items(&ALL_SPECIFICATIONS) + .with_prompt("Select cached specifications to delete") + .interact_on(&Term::stdout())?; + // run a separate tokio runtime to avoid interfering with actix's threads + let runtime = tokio::runtime::Builder::new_multi_thread() + .worker_threads(1) + .enable_all() + .build()?; + runtime.block_on(async { + for index in chosen { + let specification = ALL_SPECIFICATIONS.get(index).unwrap(); + let keys = scan_match(&pool, &format!("{}*", specification)).await?; + for key in keys { + del(&pool, &key).await?; + } + } + Ok::<(), ThothError>(()) + }) +} diff --git a/src/bin/commands/mod.rs b/src/bin/commands/mod.rs index 476f3ba9a..d75b901a5 100644 --- a/src/bin/commands/mod.rs +++ b/src/bin/commands/mod.rs @@ -1,8 +1,17 @@ -use thoth_api::db::{init_pool as init_pg_pool, PgPool}; +use thoth::api::{ + db::{init_pool as init_pg_pool, PgPool}, + redis::{init_pool as init_redis_pool, RedisPool}, +}; -pub(crate) mod account; +pub(super) mod account; +pub(super) mod cache; fn get_pg_pool(arguments: &clap::ArgMatches) -> PgPool { let database_url = arguments.get_one::("db").unwrap(); init_pg_pool(database_url) } + +fn get_redis_pool(arguments: &clap::ArgMatches) -> RedisPool { + let redis_url = arguments.get_one::("redis").unwrap(); + init_redis_pool(redis_url) +} diff --git a/src/bin/thoth.rs b/src/bin/thoth.rs index 8c19dbd70..eda04640e 100644 --- a/src/bin/thoth.rs +++ b/src/bin/thoth.rs @@ -1,15 +1,11 @@ use clap::{crate_authors, crate_version, value_parser, Arg, ArgAction, Command}; -use dialoguer::{console::Term, MultiSelect}; use dotenv::dotenv; use std::env; use thoth::{ - api::{ - db::{revert_migrations, run_migrations}, - redis::{del, init_pool as init_redis_pool, scan_match}, - }, + api::db::{revert_migrations, run_migrations}, api_server, app_server, - errors::{ThothError, ThothResult}, - export_server, ALL_SPECIFICATIONS, + errors::ThothResult, + export_server, }; mod commands; @@ -345,29 +341,7 @@ fn main() -> ThothResult<()> { _ => unreachable!(), }, Some(("cache", cache_matches)) => match cache_matches.subcommand() { - Some(("delete", _)) => { - let redis_url = cache_matches.get_one::("redis").unwrap(); - let pool = init_redis_pool(redis_url); - let chosen: Vec = MultiSelect::new() - .items(&ALL_SPECIFICATIONS) - .with_prompt("Select cached specifications to delete") - .interact_on(&Term::stdout())?; - // run a separate tokio runtime to avoid interfering with actix's threads - let runtime = tokio::runtime::Builder::new_multi_thread() - .worker_threads(1) - .enable_all() - .build()?; - runtime.block_on(async { - for index in chosen { - let specification = ALL_SPECIFICATIONS.get(index).unwrap(); - let keys = scan_match(&pool, &format!("{}*", specification)).await?; - for key in keys { - del(&pool, &key).await?; - } - } - Ok::<(), ThothError>(()) - }) - } + Some(("delete", _)) => commands::cache::delete(cache_matches), _ => unreachable!(), }, _ => unreachable!(), From 9bd3b06779541209e934fabe95bc894bcfd129f2 Mon Sep 17 00:00:00 2001 From: Javier Arias Date: Mon, 27 Jan 2025 14:25:09 +0000 Subject: [PATCH 09/23] Abstract start command --- src/bin/commands/mod.rs | 1 + src/bin/commands/start.rs | 64 +++++++++++++++++++++++++++++++++++++++ src/bin/thoth.rs | 61 ++----------------------------------- 3 files changed, 68 insertions(+), 58 deletions(-) create mode 100644 src/bin/commands/start.rs diff --git a/src/bin/commands/mod.rs b/src/bin/commands/mod.rs index d75b901a5..b61962ead 100644 --- a/src/bin/commands/mod.rs +++ b/src/bin/commands/mod.rs @@ -5,6 +5,7 @@ use thoth::api::{ pub(super) mod account; pub(super) mod cache; +pub(super) mod start; fn get_pg_pool(arguments: &clap::ArgMatches) -> PgPool { let database_url = arguments.get_one::("db").unwrap(); diff --git a/src/bin/commands/start.rs b/src/bin/commands/start.rs new file mode 100644 index 000000000..ea1c43030 --- /dev/null +++ b/src/bin/commands/start.rs @@ -0,0 +1,64 @@ +use clap::ArgMatches; +use thoth::{ + api_server, app_server, + errors::ThothResult, + export_server, +}; + +pub fn graphql_api(arguments: &ArgMatches) -> ThothResult<()> { + let database_url = arguments.get_one::("db").unwrap().to_owned(); + let host = arguments.get_one::("host").unwrap().to_owned(); + let port = arguments.get_one::("port").unwrap().to_owned(); + let threads = *arguments.get_one::("threads").unwrap(); + let keep_alive = *arguments.get_one::("keep-alive").unwrap(); + let url = arguments.get_one::("gql-url").unwrap().to_owned(); + let domain = arguments.get_one::("domain").unwrap().to_owned(); + let secret_str = arguments.get_one::("key").unwrap().to_owned(); + let session_duration = *arguments.get_one::("duration").unwrap(); + api_server( + database_url, + host, + port, + threads, + keep_alive, + url, + domain, + secret_str, + session_duration, + ) + .map_err(|e| e.into()) +} + +pub fn app(arguments: &ArgMatches) -> ThothResult<()> { + let host = arguments.get_one::("host").unwrap().to_owned(); + let port = arguments.get_one::("port").unwrap().to_owned(); + let threads = *arguments.get_one::("threads").unwrap(); + let keep_alive = *arguments.get_one::("keep-alive").unwrap(); + app_server(host, port, threads, keep_alive).map_err(|e| e.into()) +} + +pub fn export_api(arguments: &ArgMatches) -> ThothResult<()> { + let redis_url = arguments.get_one::("redis").unwrap().to_owned(); + let host = arguments.get_one::("host").unwrap().to_owned(); + let port = arguments.get_one::("port").unwrap().to_owned(); + let threads = *arguments.get_one::("threads").unwrap(); + let keep_alive = *arguments.get_one::("keep-alive").unwrap(); + let url = arguments + .get_one::("export-url") + .unwrap() + .to_owned(); + let gql_endpoint = arguments + .get_one::("gql-endpoint") + .unwrap() + .to_owned(); + export_server( + redis_url, + host, + port, + threads, + keep_alive, + url, + gql_endpoint, + ) + .map_err(|e| e.into()) +} diff --git a/src/bin/thoth.rs b/src/bin/thoth.rs index eda04640e..d77b6850a 100644 --- a/src/bin/thoth.rs +++ b/src/bin/thoth.rs @@ -240,64 +240,9 @@ fn main() -> ThothResult<()> { match thoth_commands().get_matches().subcommand() { Some(("start", start_matches)) => match start_matches.subcommand() { - Some(("graphql-api", api_matches)) => { - let database_url = api_matches.get_one::("db").unwrap().to_owned(); - let host = api_matches.get_one::("host").unwrap().to_owned(); - let port = api_matches.get_one::("port").unwrap().to_owned(); - let threads = *api_matches.get_one::("threads").unwrap(); - let keep_alive = *api_matches.get_one::("keep-alive").unwrap(); - let url = api_matches.get_one::("gql-url").unwrap().to_owned(); - let domain = api_matches.get_one::("domain").unwrap().to_owned(); - let secret_str = api_matches.get_one::("key").unwrap().to_owned(); - let session_duration = *api_matches.get_one::("duration").unwrap(); - api_server( - database_url, - host, - port, - threads, - keep_alive, - url, - domain, - secret_str, - session_duration, - ) - .map_err(|e| e.into()) - } - Some(("app", client_matches)) => { - let host = client_matches.get_one::("host").unwrap().to_owned(); - let port = client_matches.get_one::("port").unwrap().to_owned(); - let threads = *client_matches.get_one::("threads").unwrap(); - let keep_alive = *client_matches.get_one::("keep-alive").unwrap(); - app_server(host, port, threads, keep_alive).map_err(|e| e.into()) - } - Some(("export-api", client_matches)) => { - let redis_url = client_matches - .get_one::("redis") - .unwrap() - .to_owned(); - let host = client_matches.get_one::("host").unwrap().to_owned(); - let port = client_matches.get_one::("port").unwrap().to_owned(); - let threads = *client_matches.get_one::("threads").unwrap(); - let keep_alive = *client_matches.get_one::("keep-alive").unwrap(); - let url = client_matches - .get_one::("export-url") - .unwrap() - .to_owned(); - let gql_endpoint = client_matches - .get_one::("gql-endpoint") - .unwrap() - .to_owned(); - export_server( - redis_url, - host, - port, - threads, - keep_alive, - url, - gql_endpoint, - ) - .map_err(|e| e.into()) - } + Some(("graphql-api", api_matches)) => commands::start::graphql_api(api_matches), + Some(("app", client_matches)) => commands::start::app(client_matches), + Some(("export-api", client_matches)) => commands::start::export_api(client_matches), _ => unreachable!(), }, Some(("migrate", migrate_matches)) => { From dd9376e4ee27805334ade1da37b1920990b5e1c3 Mon Sep 17 00:00:00 2001 From: Javier Arias Date: Mon, 27 Jan 2025 14:50:21 +0000 Subject: [PATCH 10/23] Abstract init command --- src/bin/commands/mod.rs | 23 +++++++++++++- src/bin/commands/start.rs | 6 +--- src/bin/thoth.rs | 64 +++++++++------------------------------ 3 files changed, 38 insertions(+), 55 deletions(-) diff --git a/src/bin/commands/mod.rs b/src/bin/commands/mod.rs index b61962ead..0da344db2 100644 --- a/src/bin/commands/mod.rs +++ b/src/bin/commands/mod.rs @@ -1,7 +1,11 @@ use thoth::api::{ - db::{init_pool as init_pg_pool, PgPool}, + db::{ + init_pool as init_pg_pool, revert_migrations as revert_db_migrations, + run_migrations as run_db_migrations, PgPool, + }, redis::{init_pool as init_redis_pool, RedisPool}, }; +use thoth_errors::ThothResult; pub(super) mod account; pub(super) mod cache; @@ -16,3 +20,20 @@ fn get_redis_pool(arguments: &clap::ArgMatches) -> RedisPool { let redis_url = arguments.get_one::("redis").unwrap(); init_redis_pool(redis_url) } + +pub(super) fn migrate(arguments: &clap::ArgMatches) -> ThothResult<()> { + match arguments.get_flag("revert") { + true => revert_migrations(arguments), + false => run_migrations(arguments), + } +} + +pub(super) fn run_migrations(arguments: &clap::ArgMatches) -> ThothResult<()> { + let database_url = arguments.get_one::("db").unwrap(); + run_db_migrations(database_url) +} + +fn revert_migrations(arguments: &clap::ArgMatches) -> ThothResult<()> { + let database_url = arguments.get_one::("db").unwrap(); + revert_db_migrations(database_url) +} diff --git a/src/bin/commands/start.rs b/src/bin/commands/start.rs index ea1c43030..49af667c9 100644 --- a/src/bin/commands/start.rs +++ b/src/bin/commands/start.rs @@ -1,9 +1,5 @@ use clap::ArgMatches; -use thoth::{ - api_server, app_server, - errors::ThothResult, - export_server, -}; +use thoth::{api_server, app_server, errors::ThothResult, export_server}; pub fn graphql_api(arguments: &ArgMatches) -> ThothResult<()> { let database_url = arguments.get_one::("db").unwrap().to_owned(); diff --git a/src/bin/thoth.rs b/src/bin/thoth.rs index d77b6850a..311d53512 100644 --- a/src/bin/thoth.rs +++ b/src/bin/thoth.rs @@ -1,12 +1,7 @@ use clap::{crate_authors, crate_version, value_parser, Arg, ArgAction, Command}; use dotenv::dotenv; use std::env; -use thoth::{ - api::db::{revert_migrations, run_migrations}, - api_server, app_server, - errors::ThothResult, - export_server, -}; +use thoth::errors::ThothResult; mod commands; @@ -239,54 +234,25 @@ fn main() -> ThothResult<()> { dotenv().ok(); match thoth_commands().get_matches().subcommand() { - Some(("start", start_matches)) => match start_matches.subcommand() { - Some(("graphql-api", api_matches)) => commands::start::graphql_api(api_matches), - Some(("app", client_matches)) => commands::start::app(client_matches), - Some(("export-api", client_matches)) => commands::start::export_api(client_matches), + Some(("start", start_arguments)) => match start_arguments.subcommand() { + Some(("graphql-api", arguments)) => commands::start::graphql_api(arguments), + Some(("app", arguments)) => commands::start::app(arguments), + Some(("export-api", arguments)) => commands::start::export_api(arguments), _ => unreachable!(), }, - Some(("migrate", migrate_matches)) => { - let database_url = migrate_matches.get_one::("db").unwrap(); - match migrate_matches.get_flag("revert") { - true => revert_migrations(database_url), - false => run_migrations(database_url), - } + Some(("migrate", aguments)) => commands::migrate(aguments), + Some(("init", arguments)) => { + commands::run_migrations(arguments)?; + commands::start::graphql_api(arguments) } - Some(("init", init_matches)) => { - let database_url = init_matches.get_one::("db").unwrap().to_owned(); - let host = init_matches.get_one::("host").unwrap().to_owned(); - let port = init_matches.get_one::("port").unwrap().to_owned(); - let threads = *init_matches.get_one::("threads").unwrap(); - let keep_alive = *init_matches.get_one::("keep-alive").unwrap(); - let url = init_matches - .get_one::("gql-url") - .unwrap() - .to_owned(); - let domain = init_matches.get_one::("domain").unwrap().to_owned(); - let secret_str = init_matches.get_one::("key").unwrap().to_owned(); - let session_duration = *init_matches.get_one::("duration").unwrap(); - run_migrations(&database_url)?; - api_server( - database_url, - host, - port, - threads, - keep_alive, - url, - domain, - secret_str, - session_duration, - ) - .map_err(|e| e.into()) - } - Some(("account", account_matches)) => match account_matches.subcommand() { - Some(("register", _)) => commands::account::register(account_matches), - Some(("publishers", _)) => commands::account::publishers(account_matches), - Some(("password", _)) => commands::account::password(account_matches), + Some(("account", aguments)) => match aguments.subcommand() { + Some(("register", _)) => commands::account::register(aguments), + Some(("publishers", _)) => commands::account::publishers(aguments), + Some(("password", _)) => commands::account::password(aguments), _ => unreachable!(), }, - Some(("cache", cache_matches)) => match cache_matches.subcommand() { - Some(("delete", _)) => commands::cache::delete(cache_matches), + Some(("cache", aguments)) => match aguments.subcommand() { + Some(("delete", _)) => commands::cache::delete(aguments), _ => unreachable!(), }, _ => unreachable!(), From 46c7a670fd449af2e3dee87f8109be6dd5a8a043 Mon Sep 17 00:00:00 2001 From: Javier Arias Date: Mon, 27 Jan 2025 14:59:19 +0000 Subject: [PATCH 11/23] Abstract arguments to their own module --- src/bin/arguments/mod.rs | 133 ++++++++++++++++++++++++++ src/bin/thoth.rs | 199 +++++++-------------------------------- 2 files changed, 167 insertions(+), 165 deletions(-) create mode 100644 src/bin/arguments/mod.rs diff --git a/src/bin/arguments/mod.rs b/src/bin/arguments/mod.rs new file mode 100644 index 000000000..f8046125a --- /dev/null +++ b/src/bin/arguments/mod.rs @@ -0,0 +1,133 @@ +use clap::{value_parser, Arg}; + +pub fn database() -> Arg { + Arg::new("db") + .short('D') + .long("database-url") + .value_name("DATABASE_URL") + .env("DATABASE_URL") + .help("Full postgres database url, e.g. postgres://thoth:thoth@localhost/thoth") + .num_args(1) +} + +pub fn redis() -> Arg { + Arg::new("redis") + .short('R') + .long("redis-url") + .value_name("REDIS_URL") + .env("REDIS_URL") + .help("Full redis url, e.g. redis://localhost:6379") + .num_args(1) +} + +pub fn host(env_value: &'static str) -> Arg { + Arg::new("host") + .short('H') + .long("host") + .value_name("HOST") + .env(env_value) + .default_value("0.0.0.0") + .help("host to bind") + .num_args(1) +} + +pub fn port(default_value: &'static str, env_value: &'static str) -> Arg { + Arg::new("port") + .short('p') + .long("port") + .value_name("PORT") + .env(env_value) + .default_value(default_value) + .help("Port to bind") + .num_args(1) +} + +pub fn domain() -> Arg { + Arg::new("domain") + .short('d') + .long("domain") + .value_name("THOTH_DOMAIN") + .env("THOTH_DOMAIN") + .default_value("localhost") + .help("Authentication cookie domain") + .num_args(1) +} + +pub fn key() -> Arg { + Arg::new("key") + .short('k') + .long("secret-key") + .value_name("SECRET") + .env("SECRET_KEY") + .help("Authentication cookie secret key") + .num_args(1) +} + +pub fn session() -> Arg { + Arg::new("duration") + .short('s') + .long("session-length") + .value_name("DURATION") + .env("SESSION_DURATION_SECONDS") + .default_value("3600") + .help("Authentication cookie session duration (seconds)") + .num_args(1) + .value_parser(value_parser!(i64)) +} + +pub fn gql_url() -> Arg { + Arg::new("gql-url") + .short('u') + .long("gql-url") + .value_name("THOTH_GRAPHQL_API") + .env("THOTH_GRAPHQL_API") + .default_value("http://localhost:8000") + .help("Thoth GraphQL's, public facing, root URL.") + .num_args(1) +} + +pub fn gql_endpoint() -> Arg { + Arg::new("gql-endpoint") + .short('g') + .long("gql-endpoint") + .value_name("THOTH_GRAPHQL_ENDPOINT") + .env("THOTH_GRAPHQL_ENDPOINT") + .default_value("http://localhost:8000/graphql") + .help("Thoth GraphQL's endpoint") + .num_args(1) +} + +pub fn export_url() -> Arg { + Arg::new("export-url") + .short('u') + .long("export-url") + .value_name("THOTH_EXPORT_API") + .env("THOTH_EXPORT_API") + .default_value("http://localhost:8181") + .help("Thoth Export API's, public facing, root URL.") + .num_args(1) +} + +pub fn threads(env_value: &'static str) -> Arg { + Arg::new("threads") + .short('t') + .long("threads") + .value_name("THREADS") + .env(env_value) + .default_value("5") + .help("Number of HTTP workers to start") + .num_args(1) + .value_parser(value_parser!(usize)) +} + +pub fn keep_alive(env_value: &'static str) -> Arg { + Arg::new("keep-alive") + .short('K') + .long("keep-alive") + .value_name("THREADS") + .env(env_value) + .default_value("5") + .help("Number of seconds to wait for subsequent requests") + .num_args(1) + .value_parser(value_parser!(u64)) +} diff --git a/src/bin/thoth.rs b/src/bin/thoth.rs index 311d53512..dd2ed645d 100644 --- a/src/bin/thoth.rs +++ b/src/bin/thoth.rs @@ -1,142 +1,11 @@ -use clap::{crate_authors, crate_version, value_parser, Arg, ArgAction, Command}; +use clap::{crate_authors, crate_version, Arg, ArgAction, Command}; use dotenv::dotenv; use std::env; use thoth::errors::ThothResult; +mod arguments; mod commands; -fn database_argument() -> Arg { - Arg::new("db") - .short('D') - .long("database-url") - .value_name("DATABASE_URL") - .env("DATABASE_URL") - .help("Full postgres database url, e.g. postgres://thoth:thoth@localhost/thoth") - .num_args(1) -} - -fn redis_argument() -> Arg { - Arg::new("redis") - .short('R') - .long("redis-url") - .value_name("REDIS_URL") - .env("REDIS_URL") - .help("Full redis url, e.g. redis://localhost:6379") - .num_args(1) -} - -fn host_argument(env_value: &'static str) -> Arg { - Arg::new("host") - .short('H') - .long("host") - .value_name("HOST") - .env(env_value) - .default_value("0.0.0.0") - .help("host to bind") - .num_args(1) -} - -fn port_argument(default_value: &'static str, env_value: &'static str) -> Arg { - Arg::new("port") - .short('p') - .long("port") - .value_name("PORT") - .env(env_value) - .default_value(default_value) - .help("Port to bind") - .num_args(1) -} - -fn domain_argument() -> Arg { - Arg::new("domain") - .short('d') - .long("domain") - .value_name("THOTH_DOMAIN") - .env("THOTH_DOMAIN") - .default_value("localhost") - .help("Authentication cookie domain") - .num_args(1) -} - -fn key_argument() -> Arg { - Arg::new("key") - .short('k') - .long("secret-key") - .value_name("SECRET") - .env("SECRET_KEY") - .help("Authentication cookie secret key") - .num_args(1) -} - -fn session_argument() -> Arg { - Arg::new("duration") - .short('s') - .long("session-length") - .value_name("DURATION") - .env("SESSION_DURATION_SECONDS") - .default_value("3600") - .help("Authentication cookie session duration (seconds)") - .num_args(1) - .value_parser(value_parser!(i64)) -} - -fn gql_url_argument() -> Arg { - Arg::new("gql-url") - .short('u') - .long("gql-url") - .value_name("THOTH_GRAPHQL_API") - .env("THOTH_GRAPHQL_API") - .default_value("http://localhost:8000") - .help("Thoth GraphQL's, public facing, root URL.") - .num_args(1) -} - -fn gql_endpoint_argument() -> Arg { - Arg::new("gql-endpoint") - .short('g') - .long("gql-endpoint") - .value_name("THOTH_GRAPHQL_ENDPOINT") - .env("THOTH_GRAPHQL_ENDPOINT") - .default_value("http://localhost:8000/graphql") - .help("Thoth GraphQL's endpoint") - .num_args(1) -} - -fn export_url_argument() -> Arg { - Arg::new("export-url") - .short('u') - .long("export-url") - .value_name("THOTH_EXPORT_API") - .env("THOTH_EXPORT_API") - .default_value("http://localhost:8181") - .help("Thoth Export API's, public facing, root URL.") - .num_args(1) -} - -fn threads_argument(env_value: &'static str) -> Arg { - Arg::new("threads") - .short('t') - .long("threads") - .value_name("THREADS") - .env(env_value) - .default_value("5") - .help("Number of HTTP workers to start") - .num_args(1) - .value_parser(value_parser!(usize)) -} - -fn keep_alive_argument(env_value: &'static str) -> Arg { - Arg::new("keep-alive") - .short('K') - .long("keep-alive") - .value_name("THREADS") - .env(env_value) - .default_value("5") - .help("Number of seconds to wait for subsequent requests") - .num_args(1) - .value_parser(value_parser!(u64)) -} - fn thoth_commands() -> Command { Command::new(env!("CARGO_PKG_NAME")) .version(crate_version!()) @@ -147,7 +16,7 @@ fn thoth_commands() -> Command { .subcommand( Command::new("migrate") .about("Run the database migrations") - .arg(database_argument()) + .arg(arguments::database()) .arg( Arg::new("revert") .long("revert") @@ -163,53 +32,53 @@ fn thoth_commands() -> Command { .subcommand( Command::new("graphql-api") .about("Start the thoth GraphQL API server") - .arg(database_argument()) - .arg(host_argument("GRAPHQL_API_HOST")) - .arg(port_argument("8000", "GRAPHQL_API_PORT")) - .arg(threads_argument("GRAPHQL_API_THREADS")) - .arg(keep_alive_argument("GRAPHQL_API_KEEP_ALIVE")) - .arg(gql_url_argument()) - .arg(domain_argument()) - .arg(key_argument()) - .arg(session_argument()), + .arg(arguments::database()) + .arg(arguments::host("GRAPHQL_API_HOST")) + .arg(arguments::port("8000", "GRAPHQL_API_PORT")) + .arg(arguments::threads("GRAPHQL_API_THREADS")) + .arg(arguments::keep_alive("GRAPHQL_API_KEEP_ALIVE")) + .arg(arguments::gql_url()) + .arg(arguments::domain()) + .arg(arguments::key()) + .arg(arguments::session()), ) .subcommand( Command::new("app") .about("Start the thoth client GUI") - .arg(host_argument("APP_HOST")) - .arg(port_argument("8080", "APP_PORT")) - .arg(threads_argument("APP_THREADS")) - .arg(keep_alive_argument("APP_KEEP_ALIVE")), + .arg(arguments::host("APP_HOST")) + .arg(arguments::port("8080", "APP_PORT")) + .arg(arguments::threads("APP_THREADS")) + .arg(arguments::keep_alive("APP_KEEP_ALIVE")), ) .subcommand( Command::new("export-api") .about("Start the thoth metadata export API") - .arg(redis_argument()) - .arg(host_argument("EXPORT_API_HOST")) - .arg(port_argument("8181", "EXPORT_API_PORT")) - .arg(threads_argument("EXPORT_API_THREADS")) - .arg(keep_alive_argument("EXPORT_API_KEEP_ALIVE")) - .arg(export_url_argument()) - .arg(gql_endpoint_argument()), + .arg(arguments::redis()) + .arg(arguments::host("EXPORT_API_HOST")) + .arg(arguments::port("8181", "EXPORT_API_PORT")) + .arg(arguments::threads("EXPORT_API_THREADS")) + .arg(arguments::keep_alive("EXPORT_API_KEEP_ALIVE")) + .arg(arguments::export_url()) + .arg(arguments::gql_endpoint()), ), ) .subcommand( Command::new("init") .about("Run the database migrations and start the thoth API server") - .arg(database_argument()) - .arg(host_argument("GRAPHQL_API_HOST")) - .arg(port_argument("8000", "GRAPHQL_API_PORT")) - .arg(threads_argument("GRAPHQL_API_THREADS")) - .arg(keep_alive_argument("GRAPHQL_API_KEEP_ALIVE")) - .arg(gql_url_argument()) - .arg(domain_argument()) - .arg(key_argument()) - .arg(session_argument()), + .arg(arguments::database()) + .arg(arguments::host("GRAPHQL_API_HOST")) + .arg(arguments::port("8000", "GRAPHQL_API_PORT")) + .arg(arguments::threads("GRAPHQL_API_THREADS")) + .arg(arguments::keep_alive("GRAPHQL_API_KEEP_ALIVE")) + .arg(arguments::gql_url()) + .arg(arguments::domain()) + .arg(arguments::key()) + .arg(arguments::session()), ) .subcommand( Command::new("account") .about("Manage user accounts") - .arg(database_argument()) + .arg(arguments::database()) .subcommand_required(true) .arg_required_else_help(true) .subcommand(Command::new("register").about("Create a new user account")) @@ -222,7 +91,7 @@ fn thoth_commands() -> Command { .subcommand( Command::new("cache") .about("Manage cached records") - .arg(redis_argument()) + .arg(arguments::redis()) .subcommand_required(true) .arg_required_else_help(true) .subcommand(Command::new("delete").about("Delete cached records")), From a80644747c9927ceec2eb78fddffee57a3466a81 Mon Sep 17 00:00:00 2001 From: Javier Arias Date: Mon, 27 Jan 2025 15:27:06 +0000 Subject: [PATCH 12/23] Refactor binary --- Cargo.lock | 1 + Cargo.toml | 1 + src/bin/arguments/mod.rs | 9 ++- src/bin/commands/account.rs | 16 ++++++ src/bin/commands/cache.rs | 13 ++++- src/bin/commands/mod.rs | 38 +++++++++++-- src/bin/commands/start.rs | 43 +++++++++++++- src/bin/thoth.rs | 109 +++++------------------------------- 8 files changed, 125 insertions(+), 105 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2b2fcaede..6021ecfcb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3464,6 +3464,7 @@ dependencies = [ "clap", "dialoguer", "dotenv", + "lazy_static", "thoth-api", "thoth-api-server", "thoth-app-server", diff --git a/Cargo.toml b/Cargo.toml index 2c57467c2..57fe8a5ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,4 +23,5 @@ thoth-export-server = { version = "=0.13.5", path = "thoth-export-server" } clap = { version = "4.5.21", features = ["cargo", "env"] } dialoguer = { version = "0.11.0", features = ["password"] } dotenv = "0.15.0" +lazy_static = "1.5.0" tokio = { version = "1.43.0", features = ["rt", "rt-multi-thread", "macros"] } diff --git a/src/bin/arguments/mod.rs b/src/bin/arguments/mod.rs index f8046125a..53d75d51f 100644 --- a/src/bin/arguments/mod.rs +++ b/src/bin/arguments/mod.rs @@ -1,4 +1,4 @@ -use clap::{value_parser, Arg}; +use clap::{value_parser, Arg, ArgAction}; pub fn database() -> Arg { Arg::new("db") @@ -131,3 +131,10 @@ pub fn keep_alive(env_value: &'static str) -> Arg { .num_args(1) .value_parser(value_parser!(u64)) } + +pub fn revert() -> Arg { + Arg::new("revert") + .long("revert") + .help("Revert all database migrations") + .action(ArgAction::SetTrue) +} diff --git a/src/bin/commands/account.rs b/src/bin/commands/account.rs index 5faba8642..28fe06373 100644 --- a/src/bin/commands/account.rs +++ b/src/bin/commands/account.rs @@ -1,5 +1,8 @@ use super::get_pg_pool; +use crate::arguments; +use clap::Command; use dialoguer::{console::Term, theme::ColorfulTheme, Input, MultiSelect, Password, Select}; +use lazy_static::lazy_static; use std::collections::HashSet; use thoth::{ api::{ @@ -15,6 +18,19 @@ use thoth::{ errors::{ThothError, ThothResult}, }; +lazy_static! { + pub(crate) static ref COMMAND: Command = Command::new("account") + .about("Manage user accounts") + .arg(arguments::database()) + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand(Command::new("register").about("Create a new user account")) + .subcommand( + Command::new("publishers").about("Select which publisher(s) this account can manage"), + ) + .subcommand(Command::new("password").about("Reset a password")); +} + pub fn register(arguments: &clap::ArgMatches) -> ThothResult<()> { let pool = get_pg_pool(arguments); diff --git a/src/bin/commands/cache.rs b/src/bin/commands/cache.rs index 1cd56021a..8ae5ff60c 100644 --- a/src/bin/commands/cache.rs +++ b/src/bin/commands/cache.rs @@ -1,12 +1,23 @@ +use crate::arguments; use crate::commands::get_redis_pool; -use clap::ArgMatches; +use clap::{ArgMatches, Command}; use dialoguer::{console::Term, MultiSelect}; +use lazy_static::lazy_static; use thoth::{ api::redis::{del, scan_match}, errors::{ThothError, ThothResult}, ALL_SPECIFICATIONS, }; +lazy_static! { + pub(crate) static ref COMMAND: Command = Command::new("cache") + .about("Manage cached records") + .arg(arguments::redis()) + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand(Command::new("delete").about("Delete cached records")); +} + pub fn delete(arguments: &ArgMatches) -> ThothResult<()> { let pool = get_redis_pool(arguments); let chosen: Vec = MultiSelect::new() diff --git a/src/bin/commands/mod.rs b/src/bin/commands/mod.rs index 0da344db2..f6a585c9e 100644 --- a/src/bin/commands/mod.rs +++ b/src/bin/commands/mod.rs @@ -1,16 +1,42 @@ -use thoth::api::{ - db::{ - init_pool as init_pg_pool, revert_migrations as revert_db_migrations, - run_migrations as run_db_migrations, PgPool, +use crate::arguments; +use clap::Command; +use lazy_static::lazy_static; +use thoth::{ + api::{ + db::{ + init_pool as init_pg_pool, revert_migrations as revert_db_migrations, + run_migrations as run_db_migrations, PgPool, + }, + redis::{init_pool as init_redis_pool, RedisPool}, }, - redis::{init_pool as init_redis_pool, RedisPool}, + errors::ThothResult, }; -use thoth_errors::ThothResult; pub(super) mod account; pub(super) mod cache; pub(super) mod start; +lazy_static! { + pub(super) static ref INIT: Command = Command::new("init") + .about("Run the database migrations and start the thoth API server") + .arg(arguments::database()) + .arg(arguments::host("GRAPHQL_API_HOST")) + .arg(arguments::port("8000", "GRAPHQL_API_PORT")) + .arg(arguments::threads("GRAPHQL_API_THREADS")) + .arg(arguments::keep_alive("GRAPHQL_API_KEEP_ALIVE")) + .arg(arguments::gql_url()) + .arg(arguments::domain()) + .arg(arguments::key()) + .arg(arguments::session()); +} + +lazy_static! { + pub(super) static ref MIGRATE: Command = Command::new("migrate") + .about("Run the database migrations") + .arg(arguments::database()) + .arg(arguments::revert()); +} + fn get_pg_pool(arguments: &clap::ArgMatches) -> PgPool { let database_url = arguments.get_one::("db").unwrap(); init_pg_pool(database_url) diff --git a/src/bin/commands/start.rs b/src/bin/commands/start.rs index 49af667c9..9ef2f3c8d 100644 --- a/src/bin/commands/start.rs +++ b/src/bin/commands/start.rs @@ -1,6 +1,47 @@ -use clap::ArgMatches; +use crate::arguments; +use clap::{ArgMatches, Command}; +use lazy_static::lazy_static; use thoth::{api_server, app_server, errors::ThothResult, export_server}; +lazy_static! { + pub(crate) static ref COMMAND: Command = Command::new("start") + .about("Start an instance of Thoth API or GUI") + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("graphql-api") + .about("Start the thoth GraphQL API server") + .arg(arguments::database()) + .arg(arguments::host("GRAPHQL_API_HOST")) + .arg(arguments::port("8000", "GRAPHQL_API_PORT")) + .arg(arguments::threads("GRAPHQL_API_THREADS")) + .arg(arguments::keep_alive("GRAPHQL_API_KEEP_ALIVE")) + .arg(arguments::gql_url()) + .arg(arguments::domain()) + .arg(arguments::key()) + .arg(arguments::session()), + ) + .subcommand( + Command::new("app") + .about("Start the thoth client GUI") + .arg(arguments::host("APP_HOST")) + .arg(arguments::port("8080", "APP_PORT")) + .arg(arguments::threads("APP_THREADS")) + .arg(arguments::keep_alive("APP_KEEP_ALIVE")), + ) + .subcommand( + Command::new("export-api") + .about("Start the thoth metadata export API") + .arg(arguments::redis()) + .arg(arguments::host("EXPORT_API_HOST")) + .arg(arguments::port("8181", "EXPORT_API_PORT")) + .arg(arguments::threads("EXPORT_API_THREADS")) + .arg(arguments::keep_alive("EXPORT_API_KEEP_ALIVE")) + .arg(arguments::export_url()) + .arg(arguments::gql_endpoint()), + ); +} + pub fn graphql_api(arguments: &ArgMatches) -> ThothResult<()> { let database_url = arguments.get_one::("db").unwrap().to_owned(); let host = arguments.get_one::("host").unwrap().to_owned(); diff --git a/src/bin/thoth.rs b/src/bin/thoth.rs index dd2ed645d..918fa535b 100644 --- a/src/bin/thoth.rs +++ b/src/bin/thoth.rs @@ -1,108 +1,25 @@ -use clap::{crate_authors, crate_version, Arg, ArgAction, Command}; -use dotenv::dotenv; -use std::env; -use thoth::errors::ThothResult; - mod arguments; mod commands; -fn thoth_commands() -> Command { - Command::new(env!("CARGO_PKG_NAME")) - .version(crate_version!()) - .author(crate_authors!("\n")) +lazy_static::lazy_static! { + static ref THOTH: clap::Command = clap::Command::new(env!("CARGO_PKG_NAME")) + .version(clap::crate_version!()) + .author(clap::crate_authors!("\n")) .about(env!("CARGO_PKG_DESCRIPTION")) .subcommand_required(true) .arg_required_else_help(true) - .subcommand( - Command::new("migrate") - .about("Run the database migrations") - .arg(arguments::database()) - .arg( - Arg::new("revert") - .long("revert") - .help("Revert all database migrations") - .action(ArgAction::SetTrue), - ), - ) - .subcommand( - Command::new("start") - .about("Start an instance of Thoth API or GUI") - .subcommand_required(true) - .arg_required_else_help(true) - .subcommand( - Command::new("graphql-api") - .about("Start the thoth GraphQL API server") - .arg(arguments::database()) - .arg(arguments::host("GRAPHQL_API_HOST")) - .arg(arguments::port("8000", "GRAPHQL_API_PORT")) - .arg(arguments::threads("GRAPHQL_API_THREADS")) - .arg(arguments::keep_alive("GRAPHQL_API_KEEP_ALIVE")) - .arg(arguments::gql_url()) - .arg(arguments::domain()) - .arg(arguments::key()) - .arg(arguments::session()), - ) - .subcommand( - Command::new("app") - .about("Start the thoth client GUI") - .arg(arguments::host("APP_HOST")) - .arg(arguments::port("8080", "APP_PORT")) - .arg(arguments::threads("APP_THREADS")) - .arg(arguments::keep_alive("APP_KEEP_ALIVE")), - ) - .subcommand( - Command::new("export-api") - .about("Start the thoth metadata export API") - .arg(arguments::redis()) - .arg(arguments::host("EXPORT_API_HOST")) - .arg(arguments::port("8181", "EXPORT_API_PORT")) - .arg(arguments::threads("EXPORT_API_THREADS")) - .arg(arguments::keep_alive("EXPORT_API_KEEP_ALIVE")) - .arg(arguments::export_url()) - .arg(arguments::gql_endpoint()), - ), - ) - .subcommand( - Command::new("init") - .about("Run the database migrations and start the thoth API server") - .arg(arguments::database()) - .arg(arguments::host("GRAPHQL_API_HOST")) - .arg(arguments::port("8000", "GRAPHQL_API_PORT")) - .arg(arguments::threads("GRAPHQL_API_THREADS")) - .arg(arguments::keep_alive("GRAPHQL_API_KEEP_ALIVE")) - .arg(arguments::gql_url()) - .arg(arguments::domain()) - .arg(arguments::key()) - .arg(arguments::session()), - ) - .subcommand( - Command::new("account") - .about("Manage user accounts") - .arg(arguments::database()) - .subcommand_required(true) - .arg_required_else_help(true) - .subcommand(Command::new("register").about("Create a new user account")) - .subcommand( - Command::new("publishers") - .about("Select which publisher(s) this account can manage"), - ) - .subcommand(Command::new("password").about("Reset a password")), - ) - .subcommand( - Command::new("cache") - .about("Manage cached records") - .arg(arguments::redis()) - .subcommand_required(true) - .arg_required_else_help(true) - .subcommand(Command::new("delete").about("Delete cached records")), - ) + .subcommand(commands::MIGRATE.clone()) + .subcommand(commands::start::COMMAND.clone()) + .subcommand(commands::INIT.clone()) + .subcommand(commands::account::COMMAND.clone()) + .subcommand(commands::cache::COMMAND.clone()); } -fn main() -> ThothResult<()> { +fn main() -> thoth::errors::ThothResult<()> { // load environment variables from `.env` - dotenv().ok(); + dotenv::dotenv().ok(); - match thoth_commands().get_matches().subcommand() { + match THOTH.clone().get_matches().subcommand() { Some(("start", start_arguments)) => match start_arguments.subcommand() { Some(("graphql-api", arguments)) => commands::start::graphql_api(arguments), Some(("app", arguments)) => commands::start::app(arguments), @@ -130,5 +47,5 @@ fn main() -> ThothResult<()> { #[test] fn test_cli() { - thoth_commands().debug_assert(); + THOTH.clone().debug_assert(); } From f0815ba89a10f03d7097e2963010205be901064e Mon Sep 17 00:00:00 2001 From: Javier Arias Date: Mon, 27 Jan 2025 15:28:59 +0000 Subject: [PATCH 13/23] Update changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e63b4ef0c..0f8c4672a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,10 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Changed - - New binary submodule, `commands` + - [667](https://github.com/thoth-pub/thoth/pull/667) - Refactor binary using new submodules `commands` and `arguments` ### Added - - CLI subcommand `thoth account publishers` to modify which publisher(s) an account has access to + - [667](https://github.com/thoth-pub/thoth/pull/667) - CLI subcommand `thoth account publishers` to modify which publisher(s) an account has access to ## [[0.13.5]](https://github.com/thoth-pub/thoth/releases/tag/v0.13.5) - 2025-01-17 ### Changed From 7c0d6acb115ff32f0d80c10cd7925a975d4c6ca1 Mon Sep 17 00:00:00 2001 From: Javier Arias Date: Mon, 27 Jan 2025 15:31:45 +0000 Subject: [PATCH 14/23] Test running migrations when binary changes --- .github/workflows/run_migrations.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/run_migrations.yml b/.github/workflows/run_migrations.yml index a709689a1..b151de892 100644 --- a/.github/workflows/run_migrations.yml +++ b/.github/workflows/run_migrations.yml @@ -9,11 +9,13 @@ on: - '**up.sql' - '**down.sql' - '**db.rs' + - 'src/bin/**' pull_request: paths: - '**up.sql' - '**down.sql' - '**db.rs' + - 'src/bin/**' workflow_dispatch: env: From d624050d32ec33fddf6c2b6cb233d48baf0f56c2 Mon Sep 17 00:00:00 2001 From: Javier Arias Date: Mon, 27 Jan 2025 15:32:38 +0000 Subject: [PATCH 15/23] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f8c4672a..872e479ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Changed - [667](https://github.com/thoth-pub/thoth/pull/667) - Refactor binary using new submodules `commands` and `arguments` + - [667](https://github.com/thoth-pub/thoth/pull/667) - Trigger `run\_migrations` github action when binary source changes ### Added - [667](https://github.com/thoth-pub/thoth/pull/667) - CLI subcommand `thoth account publishers` to modify which publisher(s) an account has access to From d5ff24b4851e3913ea3feef403445783635e14f0 Mon Sep 17 00:00:00 2001 From: Javier Arias Date: Tue, 28 Jan 2025 11:50:40 +0000 Subject: [PATCH 16/23] Fix typo --- src/bin/thoth.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/bin/thoth.rs b/src/bin/thoth.rs index 918fa535b..42597884b 100644 --- a/src/bin/thoth.rs +++ b/src/bin/thoth.rs @@ -26,19 +26,19 @@ fn main() -> thoth::errors::ThothResult<()> { Some(("export-api", arguments)) => commands::start::export_api(arguments), _ => unreachable!(), }, - Some(("migrate", aguments)) => commands::migrate(aguments), + Some(("migrate", arguments)) => commands::migrate(arguments), Some(("init", arguments)) => { commands::run_migrations(arguments)?; commands::start::graphql_api(arguments) } - Some(("account", aguments)) => match aguments.subcommand() { - Some(("register", _)) => commands::account::register(aguments), - Some(("publishers", _)) => commands::account::publishers(aguments), - Some(("password", _)) => commands::account::password(aguments), + Some(("account", arguments)) => match arguments.subcommand() { + Some(("register", _)) => commands::account::register(arguments), + Some(("publishers", _)) => commands::account::publishers(arguments), + Some(("password", _)) => commands::account::password(arguments), _ => unreachable!(), }, - Some(("cache", aguments)) => match aguments.subcommand() { - Some(("delete", _)) => commands::cache::delete(aguments), + Some(("cache", arguments)) => match arguments.subcommand() { + Some(("delete", _)) => commands::cache::delete(arguments), _ => unreachable!(), }, _ => unreachable!(), From 50e73a507852d6a1fc5796abf071107c3f2545f5 Mon Sep 17 00:00:00 2001 From: Javier Arias Date: Tue, 28 Jan 2025 12:46:25 +0000 Subject: [PATCH 17/23] Flag admin accounts --- src/bin/commands/account.rs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/bin/commands/account.rs b/src/bin/commands/account.rs index 28fe06373..4924e1fbc 100644 --- a/src/bin/commands/account.rs +++ b/src/bin/commands/account.rs @@ -103,14 +103,24 @@ fn is_admin_input(publisher_name: &str) -> ThothResult { fn select_and_link_publishers(pool: &PgPool, account: &Account) -> ThothResult<()> { let publishers = all_publishers(pool)?; let publisher_accounts = account.get_publisher_accounts(pool)?; - let current_ids: HashSet<_> = publisher_accounts + let current_ids: HashSet<(_, _)> = publisher_accounts .iter() - .map(|pa| pa.publisher_id) + .map(|pa| (pa.publisher_id, pa.is_admin)) .collect(); - let items_checked: Vec<(_, bool)> = publishers + let items_checked: Vec<(_, _)> = publishers .iter() - .map(|publisher| (publisher, current_ids.contains(&publisher.publisher_id))) + .map(|p| { + let is_admin = current_ids + .iter() + .find(|(id, _)| *id == p.publisher_id) + .map_or(false, |(_, admin)| *admin); + let is_linked = current_ids.iter().any(|(id, _)| *id == p.publisher_id); + let admin_label = if is_admin { "Admin" } else { "" }; + let mut publisher = p.clone(); + publisher.publisher_name = format!("{:<65}| {}", publisher.publisher_name, admin_label); + (publisher, is_linked) + }) .collect(); let chosen: Vec = MultiSelect::new() @@ -123,7 +133,10 @@ fn select_and_link_publishers(pool: &PgPool, account: &Account) -> ThothResult<( .collect(); let to_add: Vec<_> = publishers .iter() - .filter(|p| chosen_ids.contains(&p.publisher_id) && !current_ids.contains(&p.publisher_id)) + .filter(|p| { + chosen_ids.contains(&p.publisher_id) + && !current_ids.iter().any(|(id, _)| id == &p.publisher_id) + }) .collect(); let to_remove: Vec<_> = publisher_accounts .iter() From e0834d7208f5660d1e8c0124a0cc9b7c9ace6c5f Mon Sep 17 00:00:00 2001 From: Javier Arias Date: Tue, 28 Jan 2025 12:49:14 +0000 Subject: [PATCH 18/23] Use colourful theme consistently --- src/bin/commands/account.rs | 16 ++++++++-------- src/bin/commands/cache.rs | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/bin/commands/account.rs b/src/bin/commands/account.rs index 4924e1fbc..f4c9318e8 100644 --- a/src/bin/commands/account.rs +++ b/src/bin/commands/account.rs @@ -34,21 +34,21 @@ lazy_static! { pub fn register(arguments: &clap::ArgMatches) -> ThothResult<()> { let pool = get_pg_pool(arguments); - let name = Input::new() + let name = Input::with_theme(&ColorfulTheme::default()) .with_prompt("Enter given name") .interact_on(&Term::stdout())?; - let surname = Input::new() + let surname = Input::with_theme(&ColorfulTheme::default()) .with_prompt("Enter family name") .interact_on(&Term::stdout())?; - let email = Input::new() + let email = Input::with_theme(&ColorfulTheme::default()) .with_prompt("Enter email address") .interact_on(&Term::stdout())?; let password = password_input()?; - let is_superuser: bool = Input::new() + let is_superuser: bool = Input::with_theme(&ColorfulTheme::default()) .with_prompt("Is this a superuser account") .default(false) .interact_on(&Term::stdout())?; - let is_bot: bool = Input::new() + let is_bot: bool = Input::with_theme(&ColorfulTheme::default()) .with_prompt("Is this a bot account") .default(false) .interact_on(&Term::stdout())?; @@ -85,7 +85,7 @@ fn email_selection(pool: &PgPool) -> ThothResult { } fn password_input() -> ThothResult { - Password::new() + Password::with_theme(&ColorfulTheme::default()) .with_prompt("Enter password") .with_confirmation("Confirm password", "Passwords do not match") .interact_on(&Term::stdout()) @@ -93,7 +93,7 @@ fn password_input() -> ThothResult { } fn is_admin_input(publisher_name: &str) -> ThothResult { - Input::new() + Input::with_theme(&ColorfulTheme::default()) .with_prompt(format!("Make user an admin of '{}'?", publisher_name)) .default(false) .interact_on(&Term::stdout()) @@ -123,7 +123,7 @@ fn select_and_link_publishers(pool: &PgPool, account: &Account) -> ThothResult<( }) .collect(); - let chosen: Vec = MultiSelect::new() + let chosen: Vec = MultiSelect::with_theme(&ColorfulTheme::default()) .with_prompt("Select publishers to link this account to") .items_checked(&items_checked) .interact_on(&Term::stdout())?; diff --git a/src/bin/commands/cache.rs b/src/bin/commands/cache.rs index 8ae5ff60c..c9ff9c29f 100644 --- a/src/bin/commands/cache.rs +++ b/src/bin/commands/cache.rs @@ -1,7 +1,7 @@ use crate::arguments; use crate::commands::get_redis_pool; use clap::{ArgMatches, Command}; -use dialoguer::{console::Term, MultiSelect}; +use dialoguer::{console::Term, theme::ColorfulTheme, MultiSelect}; use lazy_static::lazy_static; use thoth::{ api::redis::{del, scan_match}, @@ -20,7 +20,7 @@ lazy_static! { pub fn delete(arguments: &ArgMatches) -> ThothResult<()> { let pool = get_redis_pool(arguments); - let chosen: Vec = MultiSelect::new() + let chosen: Vec = MultiSelect::with_theme(&ColorfulTheme::default()) .items(&ALL_SPECIFICATIONS) .with_prompt("Select cached specifications to delete") .interact_on(&Term::stdout())?; From ebf88a664443550958502e9fc590d34c1efdd3dd Mon Sep 17 00:00:00 2001 From: Javier Arias Date: Tue, 28 Jan 2025 12:57:43 +0000 Subject: [PATCH 19/23] Remove redundant map_or --- src/bin/commands/account.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin/commands/account.rs b/src/bin/commands/account.rs index f4c9318e8..30d6f57e8 100644 --- a/src/bin/commands/account.rs +++ b/src/bin/commands/account.rs @@ -114,7 +114,7 @@ fn select_and_link_publishers(pool: &PgPool, account: &Account) -> ThothResult<( let is_admin = current_ids .iter() .find(|(id, _)| *id == p.publisher_id) - .map_or(false, |(_, admin)| *admin); + .is_some_and(|(_, admin)| *admin); let is_linked = current_ids.iter().any(|(id, _)| *id == p.publisher_id); let admin_label = if is_admin { "Admin" } else { "" }; let mut publisher = p.clone(); From 5eb0d34c036cf3ea6a63346773e8bd0c9e279183 Mon Sep 17 00:00:00 2001 From: Javier Arias Date: Tue, 28 Jan 2025 13:09:25 +0000 Subject: [PATCH 20/23] Label email selection with account properties --- src/bin/commands/account.rs | 20 ++++++++++++++++++-- thoth-api/src/account/service.rs | 6 +++--- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/bin/commands/account.rs b/src/bin/commands/account.rs index 30d6f57e8..310a51294 100644 --- a/src/bin/commands/account.rs +++ b/src/bin/commands/account.rs @@ -73,14 +73,30 @@ pub fn password(arguments: &clap::ArgMatches) -> ThothResult<()> { fn email_selection(pool: &PgPool) -> ThothResult { let all_emails = all_emails(pool).expect("No user accounts present in database."); + let email_labels: Vec = all_emails + .iter() + .map(|(email, is_superuser, is_bot, is_active)| { + let mut label = email.clone(); + if *is_superuser { + label.push_str(" 👑"); + } + if *is_bot { + label.push_str(" 🤖"); + } + if !is_active { + label.push_str(" ❌"); + } + label + }) + .collect(); let email_selection = Select::with_theme(&ColorfulTheme::default()) - .items(&all_emails) + .items(&email_labels) .default(0) .with_prompt("Select a user account") .interact_on(&Term::stdout())?; all_emails .get(email_selection) - .cloned() + .map(|(email, _, _, _)| email.clone()) .ok_or_else(|| ThothError::InternalError("Invalid user selection".into())) } diff --git a/thoth-api/src/account/service.rs b/thoth-api/src/account/service.rs index 5333f0155..45943391c 100644 --- a/thoth-api/src/account/service.rs +++ b/thoth-api/src/account/service.rs @@ -85,14 +85,14 @@ pub fn register( Ok(created_account) } -pub fn all_emails(pool: &PgPool) -> ThothResult> { +pub fn all_emails(pool: &PgPool) -> ThothResult> { let mut connection = pool.get()?; use crate::schema::account::dsl; let emails = dsl::account - .select(dsl::email) + .select((dsl::email, dsl::is_superuser, dsl::is_bot, dsl::is_active)) .order(dsl::email.asc()) - .load::(&mut connection) + .load::<(String, bool, bool, bool)>(&mut connection) .map_err(|_| ThothError::InternalError("Unable to load records".into()))?; Ok(emails) } From 1cbe812cddba354c2929971bf726e4c7a055e9dc Mon Sep 17 00:00:00 2001 From: Javier Arias Date: Tue, 28 Jan 2025 13:12:19 +0000 Subject: [PATCH 21/23] Use emoji to signal if an account is admin --- src/bin/commands/account.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/bin/commands/account.rs b/src/bin/commands/account.rs index 310a51294..629c637ed 100644 --- a/src/bin/commands/account.rs +++ b/src/bin/commands/account.rs @@ -132,9 +132,10 @@ fn select_and_link_publishers(pool: &PgPool, account: &Account) -> ThothResult<( .find(|(id, _)| *id == p.publisher_id) .is_some_and(|(_, admin)| *admin); let is_linked = current_ids.iter().any(|(id, _)| *id == p.publisher_id); - let admin_label = if is_admin { "Admin" } else { "" }; let mut publisher = p.clone(); - publisher.publisher_name = format!("{:<65}| {}", publisher.publisher_name, admin_label); + if is_admin { + publisher.publisher_name = format!("{} 🔑", publisher.publisher_name); + } (publisher, is_linked) }) .collect(); From cce673c8fa734858b1b328dfc9625d7c6df09dff Mon Sep 17 00:00:00 2001 From: Javier Arias Date: Tue, 28 Jan 2025 14:49:58 +0000 Subject: [PATCH 22/23] Bump v0.13.6 --- Cargo.lock | 16 ++++++++-------- Cargo.toml | 12 ++++++------ thoth-api-server/Cargo.toml | 6 +++--- thoth-api/Cargo.toml | 4 ++-- thoth-app-server/Cargo.toml | 2 +- thoth-app/Cargo.toml | 6 +++--- thoth-client/Cargo.toml | 8 ++++---- thoth-errors/Cargo.toml | 2 +- thoth-export-server/Cargo.toml | 8 ++++---- 9 files changed, 32 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6021ecfcb..2e0cc447e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3459,7 +3459,7 @@ dependencies = [ [[package]] name = "thoth" -version = "0.13.5" +version = "0.13.6" dependencies = [ "clap", "dialoguer", @@ -3475,7 +3475,7 @@ dependencies = [ [[package]] name = "thoth-api" -version = "0.13.5" +version = "0.13.6" dependencies = [ "actix-web", "argon2rs", @@ -3504,7 +3504,7 @@ dependencies = [ [[package]] name = "thoth-api-server" -version = "0.13.5" +version = "0.13.6" dependencies = [ "actix-cors", "actix-http", @@ -3522,7 +3522,7 @@ dependencies = [ [[package]] name = "thoth-app" -version = "0.13.5" +version = "0.13.6" dependencies = [ "chrono", "dotenv", @@ -3547,7 +3547,7 @@ dependencies = [ [[package]] name = "thoth-app-server" -version = "0.13.5" +version = "0.13.6" dependencies = [ "actix-cors", "actix-web", @@ -3557,7 +3557,7 @@ dependencies = [ [[package]] name = "thoth-client" -version = "0.13.5" +version = "0.13.6" dependencies = [ "chrono", "graphql_client", @@ -3573,7 +3573,7 @@ dependencies = [ [[package]] name = "thoth-errors" -version = "0.13.5" +version = "0.13.6" dependencies = [ "actix-web", "chrono", @@ -3596,7 +3596,7 @@ dependencies = [ [[package]] name = "thoth-export-server" -version = "0.13.5" +version = "0.13.6" dependencies = [ "actix-cors", "actix-web", diff --git a/Cargo.toml b/Cargo.toml index 57fe8a5ac..be855b797 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "thoth" -version = "0.13.5" +version = "0.13.6" authors = ["Javier Arias ", "Ross Higman "] edition = "2021" license = "Apache-2.0" @@ -15,11 +15,11 @@ maintenance = { status = "actively-developed" } members = ["thoth-api", "thoth-api-server", "thoth-app", "thoth-app-server", "thoth-client", "thoth-errors", "thoth-export-server"] [dependencies] -thoth-api = { version = "=0.13.5", path = "thoth-api", features = ["backend"] } -thoth-api-server = { version = "=0.13.5", path = "thoth-api-server" } -thoth-app-server = { version = "=0.13.5", path = "thoth-app-server" } -thoth-errors = { version = "=0.13.5", path = "thoth-errors" } -thoth-export-server = { version = "=0.13.5", path = "thoth-export-server" } +thoth-api = { version = "=0.13.6", path = "thoth-api", features = ["backend"] } +thoth-api-server = { version = "=0.13.6", path = "thoth-api-server" } +thoth-app-server = { version = "=0.13.6", path = "thoth-app-server" } +thoth-errors = { version = "=0.13.6", path = "thoth-errors" } +thoth-export-server = { version = "=0.13.6", path = "thoth-export-server" } clap = { version = "4.5.21", features = ["cargo", "env"] } dialoguer = { version = "0.11.0", features = ["password"] } dotenv = "0.15.0" diff --git a/thoth-api-server/Cargo.toml b/thoth-api-server/Cargo.toml index 53238b16a..1a1659566 100644 --- a/thoth-api-server/Cargo.toml +++ b/thoth-api-server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "thoth-api-server" -version = "0.13.5" +version = "0.13.6" authors = ["Javier Arias ", "Ross Higman "] edition = "2021" license = "Apache-2.0" @@ -9,8 +9,8 @@ repository = "https://github.com/thoth-pub/thoth" readme = "README.md" [dependencies] -thoth-api = { version = "=0.13.5", path = "../thoth-api", features = ["backend"] } -thoth-errors = { version = "=0.13.5", path = "../thoth-errors" } +thoth-api = { version = "=0.13.6", path = "../thoth-api", features = ["backend"] } +thoth-errors = { version = "=0.13.6", path = "../thoth-errors" } actix-web = "4.9" actix-cors = "0.7.0" actix-http = "3.9.0" diff --git a/thoth-api/Cargo.toml b/thoth-api/Cargo.toml index aa05f876f..1f7515a00 100644 --- a/thoth-api/Cargo.toml +++ b/thoth-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "thoth-api" -version = "0.13.5" +version = "0.13.6" authors = ["Javier Arias ", "Ross Higman "] edition = "2021" license = "Apache-2.0" @@ -15,7 +15,7 @@ maintenance = { status = "actively-developed" } backend = ["diesel", "diesel-derive-enum", "diesel_migrations", "futures", "actix-web", "jsonwebtoken", "deadpool-redis"] [dependencies] -thoth-errors = { version = "=0.13.5", path = "../thoth-errors" } +thoth-errors = { version = "=0.13.6", path = "../thoth-errors" } actix-web = { version = "4.9", optional = true } argon2rs = "0.2.5" isbn2 = "0.4.0" diff --git a/thoth-app-server/Cargo.toml b/thoth-app-server/Cargo.toml index 010bd6063..b57173257 100644 --- a/thoth-app-server/Cargo.toml +++ b/thoth-app-server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "thoth-app-server" -version = "0.13.5" +version = "0.13.6" authors = ["Javier Arias ", "Ross Higman "] edition = "2021" license = "Apache-2.0" diff --git a/thoth-app/Cargo.toml b/thoth-app/Cargo.toml index 314018911..74f5898a8 100644 --- a/thoth-app/Cargo.toml +++ b/thoth-app/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "thoth-app" -version = "0.13.5" +version = "0.13.6" authors = ["Javier Arias ", "Ross Higman "] edition = "2021" license = "Apache-2.0" @@ -29,8 +29,8 @@ semver = "1.0.23" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" uuid = { version = "1.11.0", features = ["serde", "v4", "js"] } -thoth-api = { version = "=0.13.5", path = "../thoth-api" } -thoth-errors = { version = "=0.13.5", path = "../thoth-errors" } +thoth-api = { version = "=0.13.6", path = "../thoth-api" } +thoth-errors = { version = "=0.13.6", path = "../thoth-errors" } [build-dependencies] dotenv = "0.15.0" diff --git a/thoth-client/Cargo.toml b/thoth-client/Cargo.toml index a36709797..c406ff1d2 100644 --- a/thoth-client/Cargo.toml +++ b/thoth-client/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "thoth-client" -version = "0.13.5" +version = "0.13.6" authors = ["Javier Arias ", "Ross Higman "] edition = "2021" license = "Apache-2.0" @@ -10,8 +10,8 @@ readme = "README.md" build = "build.rs" [dependencies] -thoth-api = {version = "=0.13.5", path = "../thoth-api" } -thoth-errors = {version = "=0.13.5", path = "../thoth-errors" } +thoth-api = {version = "=0.13.6", path = "../thoth-api" } +thoth-errors = {version = "=0.13.6", path = "../thoth-errors" } graphql_client = "0.14.0" chrono = { version = "0.4.38", features = ["serde"] } reqwest = { version = "0.12", features = ["json"] } @@ -22,4 +22,4 @@ serde_json = "1.0" uuid = { version = "1.11.0", features = ["serde"] } [build-dependencies] -thoth-api = { version = "=0.13.5", path = "../thoth-api", features = ["backend"] } +thoth-api = { version = "=0.13.6", path = "../thoth-api", features = ["backend"] } diff --git a/thoth-errors/Cargo.toml b/thoth-errors/Cargo.toml index 6cee52f9b..7b9ab564c 100644 --- a/thoth-errors/Cargo.toml +++ b/thoth-errors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "thoth-errors" -version = "0.13.5" +version = "0.13.6" authors = ["Javier Arias ", "Ross Higman "] edition = "2021" license = "Apache-2.0" diff --git a/thoth-export-server/Cargo.toml b/thoth-export-server/Cargo.toml index 47de15ea5..d715457e2 100644 --- a/thoth-export-server/Cargo.toml +++ b/thoth-export-server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "thoth-export-server" -version = "0.13.5" +version = "0.13.6" authors = ["Javier Arias ", "Ross Higman "] edition = "2021" license = "Apache-2.0" @@ -10,9 +10,9 @@ readme = "README.md" build = "build.rs" [dependencies] -thoth-api = { version = "=0.13.5", path = "../thoth-api" } -thoth-errors = { version = "=0.13.5", path = "../thoth-errors" } -thoth-client = { version = "=0.13.5", path = "../thoth-client" } +thoth-api = { version = "=0.13.6", path = "../thoth-api" } +thoth-errors = { version = "=0.13.6", path = "../thoth-errors" } +thoth-client = { version = "=0.13.6", path = "../thoth-client" } actix-web = "4.9" actix-cors = "0.7.0" cc_license = "0.1.0" From 497d2eb066cb75f3d65556ccc3a5dc004c190084 Mon Sep 17 00:00:00 2001 From: Javier Arias Date: Tue, 28 Jan 2025 14:50:24 +0000 Subject: [PATCH 23/23] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 872e479ac..75d835b19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] + +## [[0.13.6]](https://github.com/thoth-pub/thoth/releases/tag/v0.13.6) - 2025-01-28 ### Changed - [667](https://github.com/thoth-pub/thoth/pull/667) - Refactor binary using new submodules `commands` and `arguments` - [667](https://github.com/thoth-pub/thoth/pull/667) - Trigger `run\_migrations` github action when binary source changes