From 3f51d52edf1060e89921cac1598071cca8efa439 Mon Sep 17 00:00:00 2001 From: Dennis Brentjes Date: Fri, 27 Jun 2025 16:09:30 +0200 Subject: [PATCH] Refactored the connect flow and make canceling behavior more consistent. --- backend-actix/gamenight-api.yaml | 3 + .../src/request/gamenight_handlers.rs | 4 +- .../docs/AddGamenightRequestBody.md | 4 +- .../src/models/add_gamenight_request_body.rs | 14 +- gamenight-cli/src/flows/add_gamenight.rs | 39 +++--- gamenight-cli/src/flows/connect.rs | 126 ++++++++++++------ gamenight-cli/src/flows/list_gamenights.rs | 3 +- gamenight-cli/src/flows/mod.rs | 7 +- 8 files changed, 121 insertions(+), 79 deletions(-) diff --git a/backend-actix/gamenight-api.yaml b/backend-actix/gamenight-api.yaml index 2c1dcac..4e6259c 100644 --- a/backend-actix/gamenight-api.yaml +++ b/backend-actix/gamenight-api.yaml @@ -245,6 +245,9 @@ components: type: string datetime: type: string + required: + - name + - datetime GamenightId: title: GamenightId type: object diff --git a/backend-actix/src/request/gamenight_handlers.rs b/backend-actix/src/request/gamenight_handlers.rs index 4688726..1dac672 100644 --- a/backend-actix/src/request/gamenight_handlers.rs +++ b/backend-actix/src/request/gamenight_handlers.rs @@ -11,9 +11,9 @@ use crate::request::error::ApiError; impl AddGamenightRequestBody { pub fn into_with_user(&self, user: AuthUser) -> Result { Ok(gamenight::Gamenight { - datetime: DateTime::parse_from_rfc3339(&self.clone().datetime.unwrap())?.with_timezone(&chrono::Utc), + datetime: DateTime::parse_from_rfc3339(&self.datetime)?.with_timezone(&chrono::Utc), id: Uuid::new_v4(), - name: self.clone().name.unwrap().clone(), + name: self.name.clone(), owner_id: user.0.id }) } diff --git a/gamenight-api-client-rs/docs/AddGamenightRequestBody.md b/gamenight-api-client-rs/docs/AddGamenightRequestBody.md index 394e62d..357d8a5 100644 --- a/gamenight-api-client-rs/docs/AddGamenightRequestBody.md +++ b/gamenight-api-client-rs/docs/AddGamenightRequestBody.md @@ -4,8 +4,8 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- -**name** | Option<**String**> | | [optional] -**datetime** | Option<**String**> | | [optional] +**name** | **String** | | +**datetime** | **String** | | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/gamenight-api-client-rs/src/models/add_gamenight_request_body.rs b/gamenight-api-client-rs/src/models/add_gamenight_request_body.rs index 122e904..2ab053a 100644 --- a/gamenight-api-client-rs/src/models/add_gamenight_request_body.rs +++ b/gamenight-api-client-rs/src/models/add_gamenight_request_body.rs @@ -13,17 +13,17 @@ use serde::{Deserialize, Serialize}; #[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] pub struct AddGamenightRequestBody { - #[serde(rename = "name", skip_serializing_if = "Option::is_none")] - pub name: Option, - #[serde(rename = "datetime", skip_serializing_if = "Option::is_none")] - pub datetime: Option, + #[serde(rename = "name")] + pub name: String, + #[serde(rename = "datetime")] + pub datetime: String, } impl AddGamenightRequestBody { - pub fn new() -> AddGamenightRequestBody { + pub fn new(name: String, datetime: String) -> AddGamenightRequestBody { AddGamenightRequestBody { - name: None, - datetime: None, + name, + datetime, } } } diff --git a/gamenight-cli/src/flows/add_gamenight.rs b/gamenight-cli/src/flows/add_gamenight.rs index 383f63c..58791f1 100644 --- a/gamenight-cli/src/flows/add_gamenight.rs +++ b/gamenight-cli/src/flows/add_gamenight.rs @@ -19,26 +19,27 @@ impl AddGamenight { #[async_trait] impl<'a> Flow<'a> for AddGamenight { async fn run(&self, state: &'a mut GamenightState) -> FlowResult<'a> { - let mut add_gamenight = models::AddGamenightRequestBody::new(); - - add_gamenight.name = Some(Text::new("What should we call your gamenight") - .prompt()?); - let naive_date = DateSelect::new("When is your gamenight") - .prompt()?; - let naive_time = CustomType::::new("At What time") - .prompt()?; - add_gamenight.datetime = Some(naive_date - .and_time(naive_time) - .and_local_timezone(Local) - .earliest() - .unwrap() - .to_utc() - .to_rfc3339()); - - post_gamenight(&state.api_configuration, Some(add_gamenight)).await?; - clear_screen::clear(); - Ok((FlowOutcome::Successful, state)) + if let Some(name) = Text::new("What should we call your gamenight").prompt_skippable()? { + if let Some(naive_date) = DateSelect::new("When is your gamenight").prompt_skippable()? { + if let Some(naive_time) = CustomType::::new("At What time").prompt_skippable()? { + + let datetime = naive_date + .and_time(naive_time) + .and_local_timezone(Local) + .earliest() + .unwrap() + .to_utc() + .to_rfc3339(); + let add_gamenight = models::AddGamenightRequestBody::new(name, datetime); + post_gamenight(&state.api_configuration, Some(add_gamenight)).await?; + + clear_screen::clear(); + return Ok((FlowOutcome::Successful, state)) + } + } + } + Ok((FlowOutcome::Cancelled, state)) } } diff --git a/gamenight-cli/src/flows/connect.rs b/gamenight-cli/src/flows/connect.rs index b4b54ac..e336f3e 100644 --- a/gamenight-cli/src/flows/connect.rs +++ b/gamenight-cli/src/flows/connect.rs @@ -1,9 +1,10 @@ use std::fmt::Display; use async_trait::async_trait; +use gamenight_api_client_rs::apis::configuration::Configuration; use inquire::Text; -use crate::{domain::config::{Config, Instance}, flows::{gamenight_menu::GamenightMenu, login::Login}}; +use crate::{domain::config::{Config, Instance}, flows::{gamenight_menu::GamenightMenu, login::Login, FlowError}}; use super::{Flow, FlowOutcome, FlowResult, GamenightState}; @@ -24,6 +25,64 @@ impl Connect { instance: None } } + + pub fn prompt_new_instance(&self, config: &Config) -> Result, FlowError> { + let instance_name: String; + loop { + if let Some(name) = Text::new("Name this instance:").prompt_skippable()? { + if config.instances.iter().find(|x| x.name == name).is_none() { + instance_name = name; + break; + } else { + clear_screen::clear(); + println!("Name already in use, please provide a unique name"); + } + } + else { + return Ok(None); + } + } + if let Some(url) = Text::new("What is the server URL: ").prompt_skippable()? { + Ok(Some(Instance::new(instance_name, url))) + } else { + Ok(None) + } + + } + + pub async fn try_refresh_token_if_exists(&self, instance: &mut Instance, api_configuration: &mut Configuration, config: &mut Config, instance_name: &String) -> Result { + if let Some(token) = &instance.token { + api_configuration.bearer_access_token = Some(token.clone()); + let result = gamenight_api_client_rs::apis::default_api::post_token(api_configuration).await; + if let Ok(token) = result { + let instance = config.instances.iter_mut().find(|x| x.name == *instance_name).unwrap(); + instance.token = token.jwt_token.clone(); + api_configuration.bearer_access_token = token.jwt_token.clone(); + Config::save(&config)?; + Ok(true) + } + else { + Ok(false) + } + } else { + Ok(false) + } + } + + pub fn update_state_on_logon(&self, instance: &mut Instance, api_configuration: &mut Configuration, config: &mut Config, instance_name: &String) -> Result<(), FlowError> { + if self.instance.is_none() { + instance.token = Some(api_configuration.bearer_access_token.clone().unwrap()); + config.instances.push(instance.clone()); + } + else { + let instance = config.instances.iter_mut().find(|x| x.name == *instance_name).unwrap(); + instance.token = Some(api_configuration.bearer_access_token.clone().unwrap()); + } + config.last_instance = Some(instance_name.clone()); + + Config::save(&config)?; + Ok(()) + } } #[async_trait] @@ -32,58 +91,39 @@ impl<'a> Flow<'a> for Connect { let mut instance = if let Some(instance) = self.instance.clone() { instance } else { - let mut name: String; - loop { - name = Text::new("Name this instance: ").prompt()?; - if state.gamenight_configuration.instances.iter().find(|x| x.name == name).is_none() { - break; - } - clear_screen::clear(); - println!("Name already in use, please provide a unique name"); + if let Some(instance) = self.prompt_new_instance(&state.gamenight_configuration)? { + instance + } + else { + return Ok((FlowOutcome::Cancelled, state)); } - let url = Text::new("What is the server URL: ").prompt()?; - Instance::new(name, url) }; let instance_name = instance.name.clone(); - state.api_configuration.base_path = instance.url.clone(); - if let Some(token) = instance.token { - state.api_configuration.bearer_access_token = Some(token); - let result = gamenight_api_client_rs::apis::default_api::post_token(&state.api_configuration).await; - if let Ok(token) = result { - let instance = state.gamenight_configuration.instances.iter_mut().find(|x| x.name == instance_name).unwrap(); - instance.token = token.jwt_token.clone(); - state.api_configuration.bearer_access_token = token.jwt_token.clone(); - Config::save(&state.gamenight_configuration)?; - let gamenight_menu_flow = GamenightMenu::new(); - return gamenight_menu_flow.run(state).await - } - } - - let login_flow = Login::new(); - let (outcome, state) = login_flow.run(state).await?; - - if outcome == FlowOutcome::Successful { - if self.instance.is_none() { - instance.token = Some(state.api_configuration.bearer_access_token.clone().unwrap()); - state.gamenight_configuration.instances.push(instance.clone()); - } - else { - let instance = state.gamenight_configuration.instances.iter_mut().find(|x| x.name == instance_name).unwrap(); - instance.token = Some(state.api_configuration.bearer_access_token.clone().unwrap()); - } - - state.gamenight_configuration.last_instance = Some(instance_name); - - Config::save(&state.gamenight_configuration)?; + if self.try_refresh_token_if_exists(&mut instance, &mut state.api_configuration, &mut state.gamenight_configuration, &instance_name).await? { let gamenight_menu_flow = GamenightMenu::new(); gamenight_menu_flow.run(state).await - } + } else { - Ok((outcome, state)) + let login_flow = Login::new(); + let (outcome, state) = login_flow.run(state).await?; + + if outcome == FlowOutcome::Successful { + self.update_state_on_logon(&mut instance, &mut state.api_configuration, &mut state.gamenight_configuration, &instance_name)?; + + let gamenight_menu_flow = GamenightMenu::new(); + gamenight_menu_flow.run(state).await + } + else { + Ok((outcome, state)) + } } + + + + } } diff --git a/gamenight-cli/src/flows/list_gamenights.rs b/gamenight-cli/src/flows/list_gamenights.rs index 56c4950..7f6a467 100644 --- a/gamenight-cli/src/flows/list_gamenights.rs +++ b/gamenight-cli/src/flows/list_gamenights.rs @@ -38,8 +38,7 @@ impl<'a> Flow<'a> for ListGamenights { view_flows.push(Box::new(Exit::new())); - let choice = Select::new("What gamenight would you like to view?", view_flows) - .prompt_skippable()?; + let choice = Select::new("What gamenight would you like to view?", view_flows).prompt_skippable()?; clear_screen::clear(); handle_choice_option(&choice, self, state).await diff --git a/gamenight-cli/src/flows/mod.rs b/gamenight-cli/src/flows/mod.rs index d0b1530..ce55a66 100644 --- a/gamenight-cli/src/flows/mod.rs +++ b/gamenight-cli/src/flows/mod.rs @@ -97,8 +97,7 @@ impl From for FlowError { #[derive(PartialEq)] pub enum FlowOutcome { Successful, - Bool(bool), - String(String), + Cancelled, Abort } @@ -113,7 +112,7 @@ pub trait Flow<'a>: Sync + DynClone + Send + Display { async fn handle_choice<'a>(choice: &Box + Send>, flow: &dyn Flow<'a>, state: &'a mut GamenightState) -> FlowResult<'a> { let (outcome, new_state) = choice.run(state).await?; - + if outcome == FlowOutcome::Abort { Ok((FlowOutcome::Successful, new_state)) } @@ -127,6 +126,6 @@ async fn handle_choice_option<'a>(choice: &Option + Send>>, flo handle_choice(choice, flow, state).await } else { - Ok((FlowOutcome::Abort, state)) + Ok((FlowOutcome::Cancelled, state)) } } \ No newline at end of file