Fixes leave on the server and reworked login flow.
This commit is contained in:
parent
fbe456a0f5
commit
f0883a0ff0
@ -26,6 +26,19 @@ paths:
|
||||
$ref: '#/components/requestBodies/LoginRequest'
|
||||
description: Submit your credentials to get a JWT-token to use with the rest of the api.
|
||||
parameters: []
|
||||
post:
|
||||
summary: ''
|
||||
operationId: post-token
|
||||
responses:
|
||||
'200':
|
||||
$ref: '#/components/responses/TokenResponse'
|
||||
'401':
|
||||
$ref: '#/components/responses/FailureResponse'
|
||||
description: Refresh your JWT-token without logging in again.
|
||||
parameters: []
|
||||
security:
|
||||
- JWT-Auth: []
|
||||
|
||||
/user:
|
||||
post:
|
||||
summary: ''
|
||||
|
@ -38,6 +38,7 @@ async fn main() -> std::io::Result<()> {
|
||||
.wrap(TracingLogger::default())
|
||||
.app_data(web::Data::new(pool.clone()))
|
||||
.service(login)
|
||||
.service(refresh)
|
||||
.service(register)
|
||||
.service(gamenights)
|
||||
.service(gamenight_post)
|
||||
@ -45,6 +46,7 @@ async fn main() -> std::io::Result<()> {
|
||||
.service(get_user)
|
||||
.service(get_user_unauthenticated)
|
||||
.service(post_join_gamenight)
|
||||
.service(post_leave_gamenight)
|
||||
.service(get_get_participants)
|
||||
})
|
||||
.bind(("::1", 8080))?
|
||||
|
@ -6,7 +6,7 @@ use jsonwebtoken::{encode, Header, EncodingKey, decode, DecodingKey, Validation}
|
||||
use serde::{Serialize, Deserialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
use gamenight_database::{user::{get_user, Role, User}, DbPool};
|
||||
use gamenight_database::{user::{get_user, User}, DbPool};
|
||||
|
||||
use super::error::ApiError;
|
||||
|
||||
@ -16,21 +16,18 @@ pub struct Claims {
|
||||
uid: Uuid
|
||||
}
|
||||
|
||||
pub struct AuthUser {
|
||||
pub id: Uuid,
|
||||
pub username: String,
|
||||
pub email: String,
|
||||
pub role: Role,
|
||||
}
|
||||
pub struct AuthUser(pub User);
|
||||
|
||||
// pub struct AuthUser {
|
||||
// pub id: Uuid,
|
||||
// pub username: String,
|
||||
// pub email: String,
|
||||
// pub role: Role,
|
||||
// }
|
||||
|
||||
impl From<User> for AuthUser {
|
||||
fn from(value: User) -> Self {
|
||||
Self{
|
||||
id: value.id,
|
||||
username: value.username,
|
||||
email: value.email,
|
||||
role: value.role,
|
||||
}
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,7 @@ impl AddGamenightRequestBody {
|
||||
datetime: DateTime::parse_from_rfc3339(&self.clone().datetime.unwrap())?.with_timezone(&chrono::Utc),
|
||||
id: Uuid::new_v4(),
|
||||
name: self.clone().name.unwrap().clone(),
|
||||
owner_id: user.id
|
||||
owner_id: user.0.id
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ pub async fn post_join_gamenight(pool: web::Data<DbPool>, user: AuthUser, gameni
|
||||
let mut conn = pool.get_conn();
|
||||
Ok(insert_gamenight_participant(&mut conn, GamenightParticipant {
|
||||
gamenight_id: Uuid::parse_str(&gamenight_id.gamenight_id)?,
|
||||
user_id: user.id
|
||||
user_id: user.0.id
|
||||
})?)
|
||||
}).await??;
|
||||
|
||||
@ -21,10 +21,16 @@ pub async fn post_join_gamenight(pool: web::Data<DbPool>, user: AuthUser, gameni
|
||||
pub async fn post_leave_gamenight(pool: web::Data<DbPool>, user: AuthUser, gamenight_id: web::Json<GamenightId>) -> Result<impl Responder, ApiError> {
|
||||
web::block(move || -> Result<usize, ApiError> {
|
||||
let mut conn = pool.get_conn();
|
||||
Ok(delete_gamenight_participant(&mut conn, GamenightParticipant {
|
||||
let participant = GamenightParticipant {
|
||||
gamenight_id: Uuid::parse_str(&gamenight_id.gamenight_id)?,
|
||||
user_id: user.id
|
||||
})?)
|
||||
user_id: user.0.id
|
||||
};
|
||||
println!("{:?}", participant);
|
||||
let x = delete_gamenight_participant(&mut conn, participant)?;
|
||||
|
||||
println!("Amount of deleted rows: {:?}", x);
|
||||
|
||||
Ok(x)
|
||||
}).await??;
|
||||
|
||||
Ok(HttpResponse::Ok())
|
||||
|
@ -7,6 +7,7 @@ mod join_gamenight;
|
||||
mod participant_handlers;
|
||||
|
||||
pub use user_handlers::login;
|
||||
pub use user_handlers::refresh;
|
||||
pub use user_handlers::register;
|
||||
pub use gamenight_handlers::gamenights;
|
||||
pub use gamenight_handlers::gamenight_post;
|
||||
@ -14,4 +15,5 @@ pub use gamenight_handlers::gamenight_get;
|
||||
pub use user_handlers::get_user;
|
||||
pub use user_handlers::get_user_unauthenticated;
|
||||
pub use join_gamenight::post_join_gamenight;
|
||||
pub use join_gamenight::post_leave_gamenight;
|
||||
pub use participant_handlers::get_get_participants;
|
||||
|
@ -105,14 +105,22 @@ pub async fn login(pool: web::Data<DbPool>, login_data: web::Json<Login>) -> Res
|
||||
let response = Token{ jwt_token: Some(token) };
|
||||
Ok(HttpResponse::Ok()
|
||||
.content_type(ContentType::json())
|
||||
.body(serde_json::to_string(&response)?)
|
||||
)
|
||||
.body(serde_json::to_string(&response)?))
|
||||
}
|
||||
else {
|
||||
Err(ApiError{status: 401, message: "User doesn't exist or password doesn't match".to_string()})
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/token")]
|
||||
pub async fn refresh(user: AuthUser) -> Result<impl Responder, ApiError> {
|
||||
let new_token = get_token(&user.0)?;
|
||||
let response = Token{ jwt_token: Some(new_token) };
|
||||
Ok(HttpResponse::Ok()
|
||||
.content_type(ContentType::json())
|
||||
.body(serde_json::to_string(&response)?))
|
||||
}
|
||||
|
||||
#[post("/user")]
|
||||
pub async fn register(pool: web::Data<DbPool>, register_data: web::Json<Registration>) -> Result<impl Responder, ApiError> {
|
||||
web::block(move || -> Result<(), ApiError> {
|
||||
|
@ -35,6 +35,7 @@ Class | Method | HTTP request | Description
|
||||
*DefaultApi* | [**participants_get**](docs/DefaultApi.md#participants_get) | **GET** /participants | Get all participants for a gamenight
|
||||
*DefaultApi* | [**post_gamenight**](docs/DefaultApi.md#post_gamenight) | **POST** /gamenight |
|
||||
*DefaultApi* | [**post_register**](docs/DefaultApi.md#post_register) | **POST** /user |
|
||||
*DefaultApi* | [**post_token**](docs/DefaultApi.md#post_token) | **POST** /token |
|
||||
*DefaultApi* | [**user_get**](docs/DefaultApi.md#user_get) | **GET** /user |
|
||||
|
||||
|
||||
|
@ -12,6 +12,7 @@ Method | HTTP request | Description
|
||||
[**participants_get**](DefaultApi.md#participants_get) | **GET** /participants | Get all participants for a gamenight
|
||||
[**post_gamenight**](DefaultApi.md#post_gamenight) | **POST** /gamenight |
|
||||
[**post_register**](DefaultApi.md#post_register) | **POST** /user |
|
||||
[**post_token**](DefaultApi.md#post_token) | **POST** /token |
|
||||
[**user_get**](DefaultApi.md#user_get) | **GET** /user |
|
||||
|
||||
|
||||
@ -247,6 +248,33 @@ Name | Type | Description | Required | Notes
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
|
||||
## post_token
|
||||
|
||||
> models::Token post_token()
|
||||
|
||||
|
||||
Refresh your JWT-token without logging in again.
|
||||
|
||||
### Parameters
|
||||
|
||||
This endpoint does not need any parameter.
|
||||
|
||||
### Return type
|
||||
|
||||
[**models::Token**](Token.md)
|
||||
|
||||
### Authorization
|
||||
|
||||
[JWT-Auth](../README.md#JWT-Auth)
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: Not defined
|
||||
- **Accept**: application/json
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
|
||||
## user_get
|
||||
|
||||
> models::User user_get(user_id)
|
||||
|
@ -85,6 +85,14 @@ pub enum PostRegisterError {
|
||||
UnknownValue(serde_json::Value),
|
||||
}
|
||||
|
||||
/// struct for typed errors of method [`post_token`]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum PostTokenError {
|
||||
Status401(models::Failure),
|
||||
UnknownValue(serde_json::Value),
|
||||
}
|
||||
|
||||
/// struct for typed errors of method [`user_get`]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
@ -371,6 +379,44 @@ pub async fn post_register(configuration: &configuration::Configuration, registr
|
||||
}
|
||||
}
|
||||
|
||||
/// Refresh your JWT-token without logging in again.
|
||||
pub async fn post_token(configuration: &configuration::Configuration, ) -> Result<models::Token, Error<PostTokenError>> {
|
||||
|
||||
let uri_str = format!("{}/token", configuration.base_path);
|
||||
let mut req_builder = configuration.client.request(reqwest::Method::POST, &uri_str);
|
||||
|
||||
if let Some(ref user_agent) = configuration.user_agent {
|
||||
req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
|
||||
}
|
||||
if let Some(ref token) = configuration.bearer_access_token {
|
||||
req_builder = req_builder.bearer_auth(token.to_owned());
|
||||
};
|
||||
|
||||
let req = req_builder.build()?;
|
||||
let resp = configuration.client.execute(req).await?;
|
||||
|
||||
let status = resp.status();
|
||||
let content_type = resp
|
||||
.headers()
|
||||
.get("content-type")
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.unwrap_or("application/octet-stream");
|
||||
let content_type = super::ContentType::from(content_type);
|
||||
|
||||
if !status.is_client_error() && !status.is_server_error() {
|
||||
let content = resp.text().await?;
|
||||
match content_type {
|
||||
ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
|
||||
ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::Token`"))),
|
||||
ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::Token`")))),
|
||||
}
|
||||
} else {
|
||||
let content = resp.text().await?;
|
||||
let entity: Option<PostTokenError> = serde_json::from_str(&content).ok();
|
||||
Err(Error::ResponseError(ResponseContent { status, content, entity }))
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a user from primary id
|
||||
pub async fn user_get(configuration: &configuration::Configuration, user_id: Option<models::UserId>) -> Result<models::User, Error<UserGetError>> {
|
||||
// add a prefix to parameters to efficiently prevent name collisions
|
||||
|
10
gamenight-cli/Cargo.lock
generated
10
gamenight-cli/Cargo.lock
generated
@ -129,6 +129,15 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clear_screen"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da0114f83ad196a822188f44e3def7916956705df289e6634d326ad86b3f0e9c"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.7"
|
||||
@ -269,6 +278,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"chrono",
|
||||
"clear_screen",
|
||||
"dyn-clone",
|
||||
"gamenight-api-client-rs",
|
||||
"inquire",
|
||||
|
@ -14,3 +14,4 @@ uuid = { version = "1.3.0", features = ["serde", "v4"] }
|
||||
jsonwebtoken = "9.3"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
clear_screen = "0.1"
|
||||
|
85
gamenight-cli/src/domain/config.rs
Normal file
85
gamenight-cli/src/domain/config.rs
Normal file
@ -0,0 +1,85 @@
|
||||
use std::{env, fs::{self}, path::{Path, PathBuf}};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ConfigError(pub String);
|
||||
|
||||
impl From<serde_json::Error> for ConfigError {
|
||||
fn from(value: serde_json::Error) -> Self {
|
||||
Self(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for ConfigError {
|
||||
fn from(value: std::io::Error) -> Self {
|
||||
Self(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Instance {
|
||||
pub name: String,
|
||||
pub url: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(default)]
|
||||
pub token: Option<String>
|
||||
}
|
||||
|
||||
impl Instance {
|
||||
pub fn new(name: String, url: String) -> Self {
|
||||
Self {
|
||||
name,
|
||||
url,
|
||||
token: None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Config {
|
||||
pub instances: Vec<Instance>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(default)]
|
||||
pub last_instance: Option<String>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
fn config_path() -> PathBuf {
|
||||
let mut prefix = Path::new(&env::var("HOME").expect("HOME environment variable was not set")).join(".config");
|
||||
if let Ok(config_home) = env::var("XDG_CONFIG_HOME") {
|
||||
prefix = config_home.into();
|
||||
}
|
||||
|
||||
prefix.join("gamenight-cli").join("config.json")
|
||||
}
|
||||
|
||||
pub fn new() -> Config {
|
||||
Config {
|
||||
instances: vec![],
|
||||
last_instance: None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load() -> Result<Config, ConfigError> {
|
||||
let config_path = Self::config_path();
|
||||
if !fs::exists(&config_path)? {
|
||||
let config_error = ConfigError(format!("Cannot create parent directory for config file: {}", &config_path.display()).to_string());
|
||||
let _ = fs::create_dir_all(&config_path.parent().ok_or(config_error)?)?;
|
||||
|
||||
let config = Config::new();
|
||||
fs::write(&config_path, serde_json::to_string_pretty(&config)?.as_bytes())?;
|
||||
Ok(config)
|
||||
}
|
||||
else {
|
||||
let config_string = fs::read_to_string(Self::config_path())?;
|
||||
Ok(serde_json::from_str(&config_string)?)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save(gamenight_configuration: &Config) -> Result<(), ConfigError> {
|
||||
let config_path = Self::config_path();
|
||||
fs::write(&config_path, serde_json::to_string_pretty(gamenight_configuration)?.as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -13,8 +13,7 @@ pub struct Gamenight {
|
||||
|
||||
impl Display for Gamenight {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, r#"
|
||||
Name: {}
|
||||
write!(f, r#"Name: {}
|
||||
When: {}"#, self.name, self.start_time.format("%d-%m-%Y %H:%M"))
|
||||
}
|
||||
}
|
@ -1,2 +1,4 @@
|
||||
pub mod gamenight;
|
||||
pub mod user;
|
||||
pub mod user;
|
||||
pub mod config;
|
||||
pub mod participants;
|
12
gamenight-cli/src/domain/participants.rs
Normal file
12
gamenight-cli/src/domain/participants.rs
Normal file
@ -0,0 +1,12 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use crate::domain::user::User;
|
||||
|
||||
pub struct Participants<'a>(pub &'a Vec<User>);
|
||||
|
||||
impl<'a> Display for Participants<'a> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let string_list: Vec<String> = self.0.iter().map(|x| {format!("{}", x)}).collect();
|
||||
write!(f, "{}", string_list.join(", "))
|
||||
}
|
||||
}
|
@ -35,8 +35,9 @@ impl<'a> Flow<'a> for AddGamenight {
|
||||
.to_utc()
|
||||
.to_rfc3339());
|
||||
|
||||
post_gamenight(&state.configuration, Some(add_gamenight)).await?;
|
||||
post_gamenight(&state.api_configuration, Some(add_gamenight)).await?;
|
||||
|
||||
clear_screen::clear();
|
||||
Ok((FlowOutcome::Successful, state))
|
||||
}
|
||||
}
|
||||
|
99
gamenight-cli/src/flows/connect.rs
Normal file
99
gamenight-cli/src/flows/connect.rs
Normal file
@ -0,0 +1,99 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use inquire::Text;
|
||||
|
||||
use crate::{domain::config::{Config, Instance}, flows::{gamenight_menu::GamenightMenu, login::Login}};
|
||||
|
||||
use super::{Flow, FlowOutcome, FlowResult, GamenightState};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Connect {
|
||||
instance: Option<Instance>
|
||||
}
|
||||
|
||||
impl Connect {
|
||||
pub fn to(instance: Instance) -> Self {
|
||||
Self {
|
||||
instance: Some(instance)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
instance: None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<'a> Flow<'a> for Connect {
|
||||
async fn run(&self, state: &'a mut GamenightState) -> FlowResult<'a> {
|
||||
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");
|
||||
}
|
||||
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)?;
|
||||
|
||||
let gamenight_menu_flow = GamenightMenu::new();
|
||||
gamenight_menu_flow.run(state).await
|
||||
}
|
||||
else {
|
||||
Ok((outcome, state))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Connect {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if let Some(instance) = &self.instance {
|
||||
write!(f, "Connect to: {}", instance.name)
|
||||
} else {
|
||||
write!(f, "Connect")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -14,6 +14,7 @@ impl Exit {
|
||||
#[async_trait]
|
||||
impl<'a> Flow<'a> for Exit {
|
||||
async fn run(&self, state: &'a mut GamenightState) -> FlowResult<'a> {
|
||||
clear_screen::clear();
|
||||
Ok((FlowOutcome::Abort, state))
|
||||
}
|
||||
}
|
||||
|
@ -1,29 +1,35 @@
|
||||
|
||||
use inquire::{ui::RenderConfig, Select};
|
||||
|
||||
use crate::flows::{add_gamenight::AddGamenight, exit::Exit, list_gamenights::ListGamenights};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MainMenu {
|
||||
pub menu: Vec<Box<dyn for<'a> Flow<'a> + Send>>
|
||||
pub struct GamenightMenu {
|
||||
}
|
||||
|
||||
impl MainMenu {
|
||||
impl GamenightMenu {
|
||||
pub fn new() -> Self {
|
||||
MainMenu {
|
||||
menu: vec![]
|
||||
}
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for MainMenu {
|
||||
unsafe impl Send for GamenightMenu {
|
||||
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<'a> Flow<'a> for MainMenu {
|
||||
impl<'a> Flow<'a> for GamenightMenu {
|
||||
async fn run(&self, state: &'a mut GamenightState) -> FlowResult<'a> {
|
||||
let choice = Select::new("What would you like to do?", self.menu.clone())
|
||||
|
||||
let flows: Vec<Box<dyn Flow + Send>> = vec![
|
||||
Box::new(ListGamenights::new()),
|
||||
Box::new(AddGamenight::new()),
|
||||
Box::new(Exit::new())
|
||||
];
|
||||
|
||||
let choice = Select::new("What would you like to do?", flows)
|
||||
.with_help_message("Select the action you want to take or quit the program")
|
||||
.with_render_config(RenderConfig {
|
||||
option_index_prefix: inquire::ui::IndexPrefix::Simple,
|
||||
@ -31,11 +37,12 @@ impl<'a> Flow<'a> for MainMenu {
|
||||
})
|
||||
.prompt_skippable()?;
|
||||
|
||||
handle_choice_option(&choice, self, state).await
|
||||
clear_screen::clear();
|
||||
handle_choice_option(&choice, self, state).await
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for MainMenu {
|
||||
impl Display for GamenightMenu {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Main menu")
|
||||
}
|
@ -22,7 +22,9 @@ impl Join {
|
||||
#[async_trait]
|
||||
impl<'a> Flow<'a> for Join {
|
||||
async fn run(&self, state: &'a mut GamenightState) -> FlowResult<'a> {
|
||||
let _ = join_post(&state.configuration, Some(GamenightId{gamenight_id: self.gamenight_id.to_string()})).await?;
|
||||
let _ = join_post(&state.api_configuration, Some(GamenightId{gamenight_id: self.gamenight_id.to_string()})).await?;
|
||||
|
||||
clear_screen::clear();
|
||||
Ok((FlowOutcome::Successful, state))
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,9 @@ impl Leave {
|
||||
#[async_trait]
|
||||
impl<'a> Flow<'a> for Leave {
|
||||
async fn run(&self, state: &'a mut GamenightState) -> FlowResult<'a> {
|
||||
let _ = leave_post(&state.configuration, Some(GamenightId{gamenight_id: self.gamenight_id.to_string()})).await?;
|
||||
let _ = leave_post(&state.api_configuration, Some(GamenightId{gamenight_id: self.gamenight_id.to_string()})).await?;
|
||||
|
||||
clear_screen::clear();
|
||||
Ok((FlowOutcome::Successful, state))
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ impl ListGamenights {
|
||||
#[async_trait]
|
||||
impl<'a> Flow<'a> for ListGamenights {
|
||||
async fn run(&self, state: &'a mut GamenightState) -> FlowResult<'a> {
|
||||
let response = get_gamenights(&state.configuration).await?;
|
||||
let response = get_gamenights(&state.api_configuration).await?;
|
||||
|
||||
let mut view_flows: Vec<Box<dyn Flow<'_> + Send>> = vec![];
|
||||
|
||||
@ -41,6 +41,7 @@ impl<'a> Flow<'a> for ListGamenights {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -28,8 +28,10 @@ impl<'a> Flow<'a> for Login {
|
||||
let login = models::Login::new(username, password);
|
||||
|
||||
let result = get_token(&configuration, Some(login)).await?;
|
||||
|
||||
clear_screen::clear();
|
||||
if let Some(token) = result.jwt_token {
|
||||
state.configuration.bearer_access_token = Some(token);
|
||||
state.api_configuration.bearer_access_token = Some(token);
|
||||
Ok((FlowOutcome::Successful, state))
|
||||
} else {
|
||||
Err(FlowError{error: "Unexpected response".to_string()})
|
||||
|
@ -1,22 +1,19 @@
|
||||
|
||||
use super::{exit::Exit, add_gamenight::AddGamenight, list_gamenights::ListGamenights, login::Login, main_menu::MainMenu, *};
|
||||
use std::fmt::Display;
|
||||
|
||||
use inquire::{ui::RenderConfig, Select};
|
||||
|
||||
use crate::flows::{connect::Connect, exit::Exit, settings::Settings};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Main {
|
||||
login: Box<dyn for<'a> Flow<'a>>,
|
||||
main_menu: MainMenu
|
||||
}
|
||||
|
||||
impl Main {
|
||||
pub fn new() -> Self {
|
||||
let mut main_menu = MainMenu::new();
|
||||
main_menu.menu.push(Box::new(ListGamenights::new()));
|
||||
main_menu.menu.push(Box::new(AddGamenight::new()));
|
||||
main_menu.menu.push(Box::new(Exit::new()));
|
||||
Self {
|
||||
login: Box::new(Login::new()),
|
||||
main_menu
|
||||
}
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,9 +26,32 @@ impl Default for Main {
|
||||
#[async_trait]
|
||||
impl<'a> Flow<'a> for Main {
|
||||
async fn run(&self, state: &'a mut GamenightState) -> FlowResult<'a> {
|
||||
let (_outcome, state) = self.login.run(state).await?;
|
||||
let (_outcome, state) = self.main_menu.run(state).await?;
|
||||
Ok((FlowOutcome::Successful, state))
|
||||
|
||||
let config = Config::load()?;
|
||||
state.gamenight_configuration = config;
|
||||
|
||||
let mut choices: Vec<Box<dyn Flow<'a> + Send>> = vec![];
|
||||
|
||||
if let Some(last_instance) = &state.gamenight_configuration.last_instance {
|
||||
if let Some(instance) = state.gamenight_configuration.instances.iter().find(|instance| {instance.name.eq(last_instance)}) {
|
||||
choices.push(Box::new(Connect::to(instance.clone())));
|
||||
}
|
||||
}
|
||||
|
||||
choices.push(Box::new(Connect::new()));
|
||||
choices.push(Box::new(Settings::new()));
|
||||
choices.push(Box::new(Exit::new()));
|
||||
|
||||
let choice = Select::new("What would you like to do?", choices)
|
||||
.with_help_message("Select the action you want to take or quit the program")
|
||||
.with_render_config(RenderConfig {
|
||||
option_index_prefix: inquire::ui::IndexPrefix::Simple,
|
||||
..Default::default()
|
||||
})
|
||||
.prompt_skippable()?;
|
||||
|
||||
clear_screen::clear();
|
||||
handle_choice_option(&choice, self, state).await
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,29 +1,36 @@
|
||||
use std::fmt::Display;
|
||||
use std::{fmt::Display, num::ParseIntError};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use chrono::ParseError;
|
||||
use gamenight_api_client_rs::apis::{configuration::Configuration, Error};
|
||||
use inquire::InquireError;
|
||||
use dyn_clone::DynClone;
|
||||
pub use clear_screen;
|
||||
|
||||
use crate::domain::config::{Config, ConfigError};
|
||||
|
||||
pub mod main;
|
||||
mod login;
|
||||
mod main_menu;
|
||||
mod gamenight_menu;
|
||||
mod exit;
|
||||
mod list_gamenights;
|
||||
mod add_gamenight;
|
||||
mod view_gamenight;
|
||||
mod join;
|
||||
mod leave;
|
||||
mod connect;
|
||||
mod settings;
|
||||
|
||||
pub struct GamenightState {
|
||||
configuration: Configuration,
|
||||
api_configuration: Configuration,
|
||||
gamenight_configuration: Config,
|
||||
}
|
||||
|
||||
impl GamenightState {
|
||||
pub fn new() -> Self{
|
||||
Self {
|
||||
configuration: Configuration::new()
|
||||
api_configuration: Configuration::new(),
|
||||
gamenight_configuration: Config::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -71,6 +78,22 @@ impl From<uuid::Error> for FlowError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConfigError> for FlowError {
|
||||
fn from(value: ConfigError) -> Self {
|
||||
Self {
|
||||
error: value.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseIntError> for FlowError {
|
||||
fn from(value: ParseIntError) -> Self {
|
||||
Self {
|
||||
error: value.to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum FlowOutcome {
|
||||
Successful,
|
||||
|
29
gamenight-cli/src/flows/settings.rs
Normal file
29
gamenight-cli/src/flows/settings.rs
Normal file
@ -0,0 +1,29 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Settings {
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<'a> Flow<'a> for Settings {
|
||||
async fn run(&self, state: &'a mut GamenightState) -> FlowResult<'a> {
|
||||
Ok((FlowOutcome::Successful, state))
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Settings {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Settings")
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@ use jsonwebtoken::{decode, DecodingKey, Validation};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{domain::{gamenight::Gamenight, user::User}, flows::{exit::Exit, join::Join, leave::Leave}};
|
||||
use crate::{domain::{gamenight::Gamenight, participants::Participants, user::User}, flows::{exit::Exit, join::Join, leave::Leave}};
|
||||
|
||||
use super::*;
|
||||
|
||||
@ -23,11 +23,7 @@ impl ViewGamenight {
|
||||
}
|
||||
|
||||
|
||||
fn vec_user_to_usernames_string(users: &Vec<User>) -> String {
|
||||
|
||||
let string_list: Vec<String> = users.iter().map(|x| {format!("{}", x)}).collect();
|
||||
string_list.join(", ")
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Claims {
|
||||
@ -47,31 +43,31 @@ impl From<jsonwebtoken::errors::Error> for FlowError {
|
||||
impl<'a> Flow<'a> for ViewGamenight {
|
||||
async fn run(&self, state: &'a mut GamenightState) -> FlowResult<'a> {
|
||||
|
||||
let participants = participants_get(&state.configuration, Some(GamenightId{gamenight_id: self.gamenight.id.to_string()})).await?;
|
||||
let participants = participants_get(&state.api_configuration, Some(GamenightId{gamenight_id: self.gamenight.id.to_string()})).await?;
|
||||
let mut users = vec![];
|
||||
for participant in participants.participants.iter() {
|
||||
let user = user_get(&state.configuration, Some(UserId{user_id: participant.clone()})).await?;
|
||||
let user = user_get(&state.api_configuration, Some(UserId{user_id: participant.clone()})).await?;
|
||||
users.push(User {
|
||||
id: Uuid::parse_str(&user.id)?,
|
||||
username: user.username
|
||||
});
|
||||
}
|
||||
|
||||
println!("{}\nwho: {}", self.gamenight, vec_user_to_usernames_string(&users));
|
||||
println!("{}\nwho: {}", self.gamenight, Participants(&users));
|
||||
let decoding_key = DecodingKey::from_secret(b"");
|
||||
let mut validation = Validation::default();
|
||||
validation.insecure_disable_signature_validation();
|
||||
let claims = decode::<Claims>(
|
||||
&state.configuration.bearer_access_token.as_ref().unwrap(),
|
||||
&state.api_configuration.bearer_access_token.as_ref().unwrap(),
|
||||
&decoding_key,
|
||||
&validation)?.claims;
|
||||
|
||||
let join_or_leave: Box<dyn Flow<'a> + Send> =
|
||||
if users.iter().map(|x| {x.id}).find(|x| *x == claims.uid) != None {
|
||||
Box::new(Leave::new(claims.uid))
|
||||
Box::new(Leave::new(self.gamenight.id))
|
||||
}
|
||||
else {
|
||||
Box::new(Join::new(claims.uid))
|
||||
Box::new(Join::new(self.gamenight.id))
|
||||
};
|
||||
|
||||
let options: Vec<Box<dyn Flow<'a> + Send>> = vec![
|
||||
@ -81,6 +77,7 @@ impl<'a> Flow<'a> for ViewGamenight {
|
||||
let choice = Select::new("What do you want to do:", options)
|
||||
.prompt_skippable()?;
|
||||
|
||||
clear_screen::clear();
|
||||
handle_choice_option(&choice, self, state).await
|
||||
}
|
||||
}
|
||||
|
@ -2,11 +2,13 @@ pub mod flows;
|
||||
pub mod domain;
|
||||
|
||||
use flows::{main::Main, Flow, GamenightState};
|
||||
use clear_screen;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let mut state = GamenightState::new();
|
||||
let mainflow = Main::new();
|
||||
clear_screen::clear();
|
||||
if let Err(x) = mainflow.run(&mut state).await {
|
||||
println!("{}", x.error);
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
#[derive(Debug)]
|
||||
pub struct DatabaseError(pub String);
|
||||
|
||||
impl From<diesel::result::Error> for DatabaseError {
|
||||
|
@ -28,7 +28,9 @@ pub fn insert_gamenight_participant(conn: &mut DbConnection, gp: GamenightPartic
|
||||
}
|
||||
|
||||
pub fn delete_gamenight_participant(conn: &mut DbConnection, gp: GamenightParticipant) -> Result<usize, DatabaseError> {
|
||||
Ok(gamenight_participant::table
|
||||
.filter(gamenight_participant::gamenight_id.eq(&gp.gamenight_id).and(gamenight_participant::user_id.eq(gp.user_id)))
|
||||
Ok(diesel::delete(gamenight_participant::table
|
||||
.filter(gamenight_participant::gamenight_id.eq(&gp.gamenight_id)
|
||||
.and(gamenight_participant::user_id.eq(gp.user_id))))
|
||||
.execute(conn)?)
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user