Rewrite to a API trait.

This commit is contained in:
2026-01-11 14:12:54 +01:00
parent ea9f05b048
commit 88f8cf76ef
45 changed files with 1384 additions and 1030 deletions

View File

@@ -1243,9 +1243,9 @@ checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2"
[[package]]
name = "indexmap"
version = "2.12.1"
version = "2.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2"
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
dependencies = [
"equivalent",
"hashbrown",
@@ -2175,9 +2175,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "2.0.113"
version = "2.0.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "678faa00651c9eb72dd2020cbdf275d92eccb2400d568e419efdd64838145cb4"
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
dependencies = [
"proc-macro2",
"quote",
@@ -2781,18 +2781,18 @@ dependencies = [
[[package]]
name = "zerocopy"
version = "0.8.31"
version = "0.8.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3"
checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.31"
version = "0.8.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a"
checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1"
dependencies = [
"proc-macro2",
"quote",

View File

@@ -337,6 +337,8 @@ components:
type: string
name:
type: string
location_id:
type: string
datetime:
type: string
owner_id:
@@ -408,6 +410,16 @@ components:
type: string
required:
- user_id
OwnedGame:
title: OwnedGame
type: object
properties:
game_id:
type: string
location_id:
type: string
required:
- game_id
LocationId:
title: LocationId
type: object
@@ -499,7 +511,7 @@ components:
GameIdsResponse:
type: array
items:
type: string
$ref: "#/components/schemas/OwnedGame"
UserIdsResponse:
type: array
items:

View File

@@ -1,8 +1,8 @@
use gamenight_database::owned_game::OwnedGame;
use crate::game::rename_game;
use crate::owned_game::own_game;
use crate::owned_game::owned_games;
use crate::owned_game::disown_game;
use crate::owned_game::OwnedGame;
use gamenight_database::game::load_game;
use crate::game::insert_game;
use uuid::Uuid;
@@ -13,13 +13,10 @@ use gamenight_database::{
DbPool, GetConnection,
};
use crate::{
models::{
add_game_request_body::AddGameRequestBody, game::Game, game_id::GameId,
rename_game_request_body::RenameGameRequestBody, own_game_request_body::OwnGameRequestBody
},
request::{authorization::AuthUser, error::ApiError},
};
use crate::{models, models::{
add_game_request_body::AddGameRequestBody, game::Game, game_id::GameId,
rename_game_request_body::RenameGameRequestBody, own_game_request_body::OwnGameRequestBody
}, request::{authorization::AuthUser, error::ApiError}};
#[get("/games")]
pub async fn get_games(
@@ -162,7 +159,11 @@ pub async fn get_owned_games(
let mut conn = pool.get_conn();
let game_ids = owned_games(&mut conn, user.0.id)?;
let model: Vec<String> = game_ids.iter().map(|x| x.to_string()).collect();
let model = game_ids.iter().map(|(u, l)| models::owned_game::OwnedGame {
game_id: u.to_string(),
location_id: l.map(|x| x.to_string())
}).collect::<Vec<models::owned_game::OwnedGame>>();
Ok(HttpResponse::Ok()
.content_type(ContentType::json())

View File

@@ -43,6 +43,7 @@ pub async fn gamenights(
.map(|x| Gamenight {
id: x.id.to_string(),
name: x.name.clone(),
location_id: x.location_id.map(|x| x.to_string()),
datetime: x.datetime.to_rfc3339(),
owner_id: x.owner_id.to_string(),
})
@@ -77,6 +78,7 @@ pub async fn gamenight_get(
let model = Gamenight {
id: gamenight.id.to_string(),
datetime: gamenight.datetime.to_rfc3339(),
location_id: gamenight.location_id.map(|x| x.to_string()),
name: gamenight.name,
owner_id: gamenight.owner_id.to_string(),
};

View File

@@ -17,6 +17,7 @@ docs/Location.md
docs/LocationId.md
docs/Login.md
docs/OwnGameRequestBody.md
docs/OwnedGame.md
docs/Participants.md
docs/Registration.md
docs/RenameGameRequestBody.md
@@ -43,6 +44,7 @@ src/models/location_id.rs
src/models/login.rs
src/models/mod.rs
src/models/own_game_request_body.rs
src/models/owned_game.rs
src/models/participants.rs
src/models/registration.rs
src/models/rename_game_request_body.rs

View File

@@ -11,8 +11,8 @@ serde = { version = "^1.0", features = ["derive"] }
serde_json = "^1.0"
serde_repr = "^0.1"
url = "^2.5"
reqwest = { version = "^0.12", default-features = false, features = ["json", "multipart"] }
async-trait = "^0.1"
reqwest = { version = "^0.12", default-features = false, features = ["json", "multipart", "stream"] }
[features]
default = ["native-tls"]
native-tls = ["reqwest/native-tls"]

View File

@@ -69,6 +69,7 @@ Class | Method | HTTP request | Description
- [LocationId](docs/LocationId.md)
- [Login](docs/Login.md)
- [OwnGameRequestBody](docs/OwnGameRequestBody.md)
- [OwnedGame](docs/OwnedGame.md)
- [Participants](docs/Participants.md)
- [Registration](docs/Registration.md)
- [RenameGameRequestBody](docs/RenameGameRequestBody.md)

View File

@@ -4,7 +4,20 @@ fn main() {
println!("cargo::rerun-if-changed=../backend-actix/gamenight-api.yaml");
let _ =
Command::new("openapi-generator")
.args(["generate", "-i", "../backend-actix/gamenight-api.yaml", "-g", "rust", "--additional-properties=withSeparateModelsAndApi=true,modelPackage=gamenight_model,apiPackage=gamenight_api,packageName=gamenight-api-client-rs,packageVersion=0.1.0"])
.args([
"generate",
"-i",
"../backend-actix/gamenight-api.yaml",
"-g",
"rust",
"--additional-properties=\
withSeparateModelsAndApi=true,\
library=reqwest-trait,\
modelPackage=gamenight_model,\
apiPackage=gamenight_api,\
packageName=gamenight-api-client-rs,\
packageVersion=0.1.0"
])
.output()
.expect("Failed to generate models sources for the gamenight API");
}

View File

@@ -476,7 +476,7 @@ Name | Type | Description | Required | Notes
## owned_games_get
> Vec<String> owned_games_get(user_id)
> Vec<models::OwnedGame> owned_games_get(user_id)
### Parameters
@@ -488,7 +488,7 @@ Name | Type | Description | Required | Notes
### Return type
**Vec<String>**
[**Vec<models::OwnedGame>**](OwnedGame.md)
### Authorization

View File

@@ -6,6 +6,7 @@ Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**id** | **String** | |
**name** | **String** | |
**location_id** | Option<**String**> | | [optional]
**datetime** | **String** | |
**owner_id** | **String** | |

View File

@@ -0,0 +1,12 @@
# OwnedGame
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**game_id** | **String** | |
**location_id** | Option<**String**> | | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

File diff suppressed because it is too large Load Diff

View File

@@ -114,3 +114,4 @@ impl From<&str> for ContentType {
pub mod default_api;
pub mod configuration;

View File

@@ -5,7 +5,6 @@ extern crate serde_repr;
extern crate serde;
extern crate serde_json;
extern crate url;
extern crate reqwest;
pub mod apis;
pub mod models;

View File

@@ -17,6 +17,8 @@ pub struct Gamenight {
pub id: String,
#[serde(rename = "name")]
pub name: String,
#[serde(rename = "location_id", skip_serializing_if = "Option::is_none")]
pub location_id: Option<String>,
#[serde(rename = "datetime")]
pub datetime: String,
#[serde(rename = "owner_id")]
@@ -28,6 +30,7 @@ impl Gamenight {
Gamenight {
id,
name,
location_id: None,
datetime,
owner_id,
}

View File

@@ -26,6 +26,8 @@ pub mod login;
pub use self::login::Login;
pub mod own_game_request_body;
pub use self::own_game_request_body::OwnGameRequestBody;
pub mod owned_game;
pub use self::owned_game::OwnedGame;
pub mod participants;
pub use self::participants::Participants;
pub mod registration;

View File

@@ -0,0 +1,30 @@
/*
* Gamenight
*
* Api specification for a Gamenight server
*
* The version of the OpenAPI document: 1.0
* Contact: dennis@brentj.es
* Generated by: https://openapi-generator.tech
*/
use crate::models;
use serde::{Deserialize, Serialize};
#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
pub struct OwnedGame {
#[serde(rename = "game_id")]
pub game_id: String,
#[serde(rename = "location_id", skip_serializing_if = "Option::is_none")]
pub location_id: Option<String>,
}
impl OwnedGame {
pub fn new(game_id: String) -> OwnedGame {
OwnedGame {
game_id,
location_id: None,
}
}
}

View File

@@ -108,6 +108,15 @@ dependencies = [
"cc",
]
[[package]]
name = "colored"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e"
dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "core-foundation"
version = "0.9.4"
@@ -221,21 +230,6 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "futures"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
dependencies = [
"futures-channel",
"futures-core",
"futures-executor",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-channel"
version = "0.3.31"
@@ -243,7 +237,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
dependencies = [
"futures-core",
"futures-sink",
]
[[package]]
@@ -252,17 +245,6 @@ version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
[[package]]
name = "futures-executor"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-io"
version = "0.3.31"
@@ -298,7 +280,6 @@ version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-macro",
@@ -332,6 +313,7 @@ dependencies = [
name = "gamenight-api-client-rs"
version = "0.1.0"
dependencies = [
"async-trait",
"reqwest",
"serde",
"serde_json",
@@ -346,8 +328,8 @@ dependencies = [
"async-trait",
"chrono",
"clear_screen",
"colored",
"dyn-clone",
"futures",
"gamenight-api-client-rs",
"inquire",
"jsonwebtoken",
@@ -996,12 +978,14 @@ dependencies = [
"sync_wrapper",
"tokio",
"tokio-native-tls",
"tokio-util",
"tower",
"tower-http",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"wasm-streams",
"web-sys",
]
@@ -1236,9 +1220,9 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
name = "syn"
version = "2.0.113"
version = "2.0.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "678faa00651c9eb72dd2020cbdf275d92eccb2400d568e419efdd64838145cb4"
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
dependencies = [
"proc-macro2",
"quote",
@@ -1386,6 +1370,19 @@ dependencies = [
"tokio",
]
[[package]]
name = "tokio-util"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098"
dependencies = [
"bytes",
"futures-core",
"futures-sink",
"pin-project-lite",
"tokio",
]
[[package]]
name = "tower"
version = "0.5.2"
@@ -1604,6 +1601,19 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "wasm-streams"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65"
dependencies = [
"futures-util",
"js-sys",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]]
name = "web-sys"
version = "0.3.83"

View File

@@ -15,4 +15,4 @@ jsonwebtoken = "9.3"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
clear_screen = "0.1"
futures = "0.3"
colored = "3.0"

View File

@@ -0,0 +1,20 @@
use std::fmt::Display;
use colored::Colorize;
use crate::domain::location::Location;
use crate::domain::owned_games::OwnedGames;
pub struct EligableGames<'a>(pub &'a OwnedGames, pub &'a Location);
impl Display for EligableGames<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for (k, v) in self.0.0.iter() {
writeln!(f, "{}:", k)?;
for (g, l) in v.iter() {
if l.as_ref().is_none_or(|x| x.id == self.1.id) {
writeln!(f, "\t{}", g.name.green())?;
}
}
}
Ok(())
}
}

View File

@@ -1,6 +1,8 @@
use std::fmt::{Display, Formatter};
use chrono::{DateTime, Local};
use uuid::Uuid;
use crate::domain::eligable_games::EligableGames;
use crate::domain::location::Location;
use crate::domain::owned_games::OwnedGames;
use crate::domain::participants::Participants;
use crate::domain::user::User;
@@ -9,6 +11,7 @@ use crate::domain::user::User;
pub struct Gamenight {
pub id: Uuid,
pub name: String,
pub location: Option<Location>,
pub start_time: DateTime<Local>,
pub organizer: User,
pub participants: Participants,
@@ -21,7 +24,12 @@ impl Display for Gamenight {
writeln!(f, "Organizer: {}", self.organizer)?;
writeln!(f, "Start: {}", self.start_time.to_rfc3339())?;
writeln!(f, "Participants: {}", self.participants)?;
write!(f, "Owned games:\n{}", self.owned_games)?;
if let Some(location) = &self.location {
write!(f, "Eligable games:\n{}", EligableGames(&self.owned_games, location))?;
} else {
write!(f, "Owned games: \n{}", self.owned_games)?;
}
Ok(())
}
}

View File

@@ -7,6 +7,7 @@ use uuid::Uuid;
pub struct GamenightSelectData {
pub id: Uuid,
pub name: String,
pub location_id: Option<Uuid>,
pub start_time: DateTime<Local>,
pub owner_id: Uuid,
}

View File

@@ -2,6 +2,7 @@ use std::fmt::Display;
use gamenight_api_client_rs::models;
use uuid::Uuid;
use crate::flows::FlowError;
#[derive(Clone)]
pub struct Location {
@@ -11,14 +12,15 @@ pub struct Location {
pub note: Option<String>,
}
impl From<models::Location> for Location {
fn from(location: models::Location) -> Self {
Self {
id: Uuid::parse_str(&location.id).unwrap(),
name: location.name,
address: location.address,
note: location.note
}
impl TryFrom<models::Location> for Location {
type Error = FlowError;
fn try_from(value: models::Location) -> Result<Self, Self::Error> {
Ok(Self {
id: Uuid::parse_str(&value.id)?,
name: value.name,
address: value.address,
note: value.note
})
}
}

View File

@@ -6,4 +6,5 @@ pub mod game;
pub mod owned_games;
pub mod location;
pub mod location_select_data;
pub mod gamenight;
pub mod gamenight;
mod eligable_games;

View File

@@ -1,15 +1,16 @@
use std::{collections::HashMap, fmt::Display};
use crate::domain::game::Game;
use crate::domain::location::Location;
#[derive(Clone)]
pub struct OwnedGames(pub HashMap<String, Vec<Game>>);
pub struct OwnedGames (pub HashMap<String, Vec<(Game, Option<Location>)>>);
impl Display for OwnedGames {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for (k,v) in &self.0 {
write!(f, "{k}:\n")?;
for g in v {
for (g, _) in v {
write!(f, "\t{}\n", g.name)?;
}
}

View File

@@ -3,7 +3,7 @@ use std::fmt::Display;
use super::*;
use crate::flows::own::Own;
use async_trait::async_trait;
use gamenight_api_client_rs::{apis::default_api::game_post, models::AddGameRequestBody};
use gamenight_api_client_rs::models::AddGameRequestBody;
use inquire::Text;
@@ -24,7 +24,7 @@ impl<'a> Flow<'a> for AddGame {
let add_game_request = AddGameRequestBody {
name
};
let game_id_response = game_post(&state.api_configuration, Some(add_game_request)).await?;
let game_id_response = state.api.game_post(Some(add_game_request)).await?;
let own_flow = Own::new(Uuid::parse_str(&game_id_response.game_id)?);
return self.continue_with(state, &own_flow).await;

View File

@@ -1,8 +1,7 @@
use gamenight_api_client_rs::{apis::default_api::post_gamenight, models};
use inquire::{CustomType, DateSelect, Text};
use chrono::{self, Local, NaiveTime};
use gamenight_api_client_rs::models;
use super::*;
#[derive(Clone)]
@@ -32,7 +31,7 @@ impl<'a> Flow<'a> for AddGamenight {
.to_utc()
.to_rfc3339();
let add_gamenight = models::AddGamenightRequestBody::new(name, datetime);
post_gamenight(&state.api_configuration, Some(add_gamenight)).await?;
state.api.post_gamenight(Some(add_gamenight)).await?;
clear_screen::clear();
return Ok((FlowOutcome::Successful, state))

View File

@@ -1,7 +1,7 @@
use std::{ffi::OsStr, fmt::Display};
use async_trait::async_trait;
use gamenight_api_client_rs::{apis::default_api::{location_authorize_post, location_post}, models::{authorize_location_request_body::Op::Grant, AddLocationRequestBody, AuthorizeLocationRequestBody}};
use gamenight_api_client_rs::models::{authorize_location_request_body::Op::Grant, AddLocationRequestBody, AuthorizeLocationRequestBody};
use inquire::{Editor, Text};
@@ -44,7 +44,7 @@ impl<'a> Flow<'a> for AddLocation {
note
};
let location_id = location_post(&state.api_configuration, Some(add_location_request)).await?;
let location_id = state.api.location_post(Some(add_location_request)).await?;
let add_authorize_request = AuthorizeLocationRequestBody {
location_id: location_id.location_id.to_string(),
@@ -52,7 +52,7 @@ impl<'a> Flow<'a> for AddLocation {
op: Grant
};
location_authorize_post(&state.api_configuration, Some(add_authorize_request)).await?;
state.api.location_authorize_post(Some(add_authorize_request)).await?;
}
Ok((FlowOutcome::Cancelled, state))
}

View File

@@ -1,12 +1,9 @@
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, FlowError}};
use super::{Flow, FlowOutcome, FlowResult, GamenightState};
use crate::{domain::config::{Config, Instance}, flows::{gamenight_menu::GamenightMenu, login::Login, FlowError}};
use async_trait::async_trait;
use inquire::Text;
#[derive(Clone)]
pub struct Connect {
@@ -50,37 +47,37 @@ impl Connect {
}
pub async fn try_refresh_token_if_exists(&self, instance: &mut Instance, api_configuration: &mut Configuration, config: &mut Config, instance_name: &String) -> Result<bool, FlowError> {
pub async fn try_refresh_token_if_exists<'a>(&self, instance: &mut Instance, state: &'a mut GamenightState, instance_name: &String) -> Result<(bool, &'a mut GamenightState), FlowError> {
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;
let state = state.set_bearer_access_token(Some(token.clone()));
let result = state.api.post_token().await;
if let Ok(token) = result {
let instance = config.instances.iter_mut().find(|x| x.name == *instance_name).unwrap();
let instance = state.gamenight_configuration.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)
let state = state.set_bearer_access_token(token.jwt_token.clone());
Config::save(&state.gamenight_configuration)?;
Ok((true, state))
}
else {
Ok(false)
Ok((false, state))
}
} else {
Ok(false)
Ok((false, state))
}
}
pub fn update_state_on_logon(&self, instance: &mut Instance, api_configuration: &mut Configuration, config: &mut Config, instance_name: &String) -> Result<(), FlowError> {
pub fn update_state_on_logon(&self, instance: &mut Instance, state: &mut GamenightState, 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());
instance.token = Some(state.api_configuration.bearer_access_token.clone().unwrap());
state.gamenight_configuration.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());
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());
}
config.last_instance = Some(instance_name.clone());
state.gamenight_configuration.last_instance = Some(instance_name.clone());
Config::save(config)?;
Config::save(&state.gamenight_configuration)?;
Ok(())
}
}
@@ -98,9 +95,10 @@ impl<'a> Flow<'a> for Connect {
};
let instance_name = instance.name.clone();
state.api_configuration.base_path = instance.url.clone();
let state = state.set_api_base_path(instance.url.clone());
if self.try_refresh_token_if_exists(&mut instance, &mut state.api_configuration, &mut state.gamenight_configuration, &instance_name).await? {
let (token_refreshed, state) = self.try_refresh_token_if_exists(&mut instance, state, &instance_name).await?;
if token_refreshed {
let gamenight_menu_flow = GamenightMenu::new();
gamenight_menu_flow.run(state).await
}
@@ -109,7 +107,7 @@ impl<'a> Flow<'a> for Connect {
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)?;
self.update_state_on_logon(&mut instance, state, &instance_name)?;
let gamenight_menu_flow = GamenightMenu::new();
gamenight_menu_flow.run(state).await

View File

@@ -1,7 +1,7 @@
use std::fmt::Display;
use async_trait::async_trait;
use gamenight_api_client_rs::{apis::default_api::disown_post, models::GameId};
use gamenight_api_client_rs::models::GameId;
use uuid::Uuid;
use super::{Flow, FlowOutcome, FlowResult, GamenightState};
@@ -22,7 +22,7 @@ impl Disown {
#[async_trait]
impl<'a> Flow<'a> for Disown {
async fn run(&self, state: &'a mut GamenightState) -> FlowResult<'a> {
let _ = disown_post(&state.api_configuration, Some(GameId{game_id: self.game_id.to_string()})).await?;
let _ = state.api.disown_post(Some(GameId{game_id: self.game_id.to_string()})).await?;
clear_screen::clear();
Ok((FlowOutcome::Successful, state))

View File

@@ -1,7 +1,7 @@
use std::fmt::Display;
use async_trait::async_trait;
use gamenight_api_client_rs::{apis::default_api::join_post, models::GamenightId};
use gamenight_api_client_rs::models::GamenightId;
use uuid::Uuid;
use super::{Flow, FlowOutcome, FlowResult, GamenightState};
@@ -22,7 +22,7 @@ impl Join {
#[async_trait]
impl<'a> Flow<'a> for Join {
async fn run(&self, state: &'a mut GamenightState) -> FlowResult<'a> {
let _ = join_post(&state.api_configuration, Some(GamenightId{gamenight_id: self.gamenight_id.to_string()})).await?;
let _ = state.api.join_post(Some(GamenightId{gamenight_id: self.gamenight_id.to_string()})).await?;
clear_screen::clear();
Ok((FlowOutcome::Successful, state))

View File

@@ -1,7 +1,7 @@
use std::fmt::Display;
use async_trait::async_trait;
use gamenight_api_client_rs::{apis::default_api::leave_post, models::GamenightId};
use gamenight_api_client_rs::models::GamenightId;
use uuid::Uuid;
use super::{Flow, FlowOutcome, FlowResult, GamenightState};
@@ -22,7 +22,7 @@ impl Leave {
#[async_trait]
impl<'a> Flow<'a> for Leave {
async fn run(&self, state: &'a mut GamenightState) -> FlowResult<'a> {
let _ = leave_post(&state.api_configuration, Some(GamenightId{gamenight_id: self.gamenight_id.to_string()})).await?;
let _ = state.api.leave_post(Some(GamenightId{gamenight_id: self.gamenight_id.to_string()})).await?;
clear_screen::clear();
Ok((FlowOutcome::Successful, state))

View File

@@ -1,6 +1,5 @@
use chrono::DateTime;
use gamenight_api_client_rs::apis::default_api::get_gamenights;
use inquire::Select;
use uuid::Uuid;
@@ -22,7 +21,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.api_configuration).await?;
let response = state.api.get_gamenights().await?;
let mut view_flows: Vec<Box<dyn Flow<'_> + Send>> = vec![];
@@ -30,6 +29,10 @@ impl<'a> Flow<'a> for ListGamenights {
let gamenight = GamenightSelectData {
id: Uuid::parse_str(&response.id)?,
name: response.name.clone(),
location_id: match &response.location_id {
None => None,
Some(x) => Some(Uuid::parse_str(x)?)
},
start_time:DateTime::parse_from_rfc3339(&response.datetime)?.into(),
owner_id: Uuid::parse_str(&response.owner_id)?
};

View File

@@ -1,7 +1,6 @@
use std::fmt::Display;
use async_trait::async_trait;
use gamenight_api_client_rs::apis::default_api::games_get;
use inquire::{ui::RenderConfig, Select};
use crate::flows::{view_game::ViewGame, exit::Exit};
@@ -22,7 +21,7 @@ impl ListGames {
#[async_trait]
impl<'a> Flow<'a> for ListGames {
async fn run(&self, state: &'a mut GamenightState) -> FlowResult<'a> {
let games = games_get(&state.api_configuration).await?;
let games = state.api.games_get().await?;
let mut flows = games.into_iter().map(|game| -> Box<dyn Flow + Send> {
Box::new(ViewGame::new(game.into()))

View File

@@ -1,7 +1,6 @@
use std::fmt::Display;
use async_trait::async_trait;
use gamenight_api_client_rs::apis::default_api::locations_get;
use inquire::{ui::RenderConfig, Select};
use crate::flows::{exit::Exit, view_location::ViewLocation};
@@ -22,10 +21,10 @@ impl ListLocations {
#[async_trait]
impl<'a> Flow<'a> for ListLocations {
async fn run(&self, state: &'a mut GamenightState) -> FlowResult<'a> {
let locations = locations_get(&state.api_configuration).await?;
let locations = state.api.locations_get().await?;
let mut flows = locations.into_iter().map(|location| -> Box<dyn Flow + Send> {
Box::new(ViewLocation::new(location.into()))
Box::new(ViewLocation::new(location.try_into().unwrap()))
}).collect::<Vec::<Box::<dyn Flow + Send>>>();
flows.push(Box::new(Exit::new()));

View File

@@ -1,7 +1,7 @@
use std::fmt::Display;
use async_trait::async_trait;
use gamenight_api_client_rs::{apis::default_api::{self, authorized_location_user_ids_get, users_get}, models::{AuthorizeLocationRequestBody, LocationId, User}};
use gamenight_api_client_rs::models::{AuthorizeLocationRequestBody, LocationId, User};
use inquire::MultiSelect;
use uuid::Uuid;
@@ -47,11 +47,11 @@ impl<'a> TryFrom<&'a User> for AuthorizeMultiSelectStruct<'a> {
#[async_trait]
impl<'a> Flow<'a> for LocationAuthorize {
async fn run(&self, state: &'a mut GamenightState) -> FlowResult<'a> {
let users = users_get(&state.api_configuration).await?;
let users = state.api.users_get().await?;
let location_id = LocationId {
location_id: self.location_id.to_string()
};
let authorized_user_ids = authorized_location_user_ids_get(&state.api_configuration, Some(location_id)).await?;
let authorized_user_ids = state.api.authorized_location_user_ids_get(Some(location_id)).await?;
let authorized_user_ids : Vec<String> = authorized_user_ids.into_iter().map(|x| x.user_id).collect();
let options: Vec<AuthorizeMultiSelectStruct> = users.iter().map(|x| {x.try_into()}).collect::<Result<Vec<AuthorizeMultiSelectStruct>, FlowError>>()?;
@@ -67,7 +67,7 @@ impl<'a> Flow<'a> for LocationAuthorize {
if let Some(selections) = &selections {
for selection in selections {
if authorized_users.iter().find(|x| {x.id == selection.id.to_string()}).is_none() {
default_api::location_authorize_post(&state.api_configuration, Some(
state.api.location_authorize_post(Some(
AuthorizeLocationRequestBody {
location_id: self.location_id.to_string(),
user_id: selection.id.to_string(),
@@ -78,7 +78,7 @@ impl<'a> Flow<'a> for LocationAuthorize {
}
for authorized_user in authorized_users {
if selections.iter().find(|x| {x.id.to_string() == authorized_user.id}).is_none() {
default_api::location_authorize_post(&state.api_configuration, Some(
state.api.location_authorize_post(Some(
AuthorizeLocationRequestBody {
location_id: self.location_id.to_string(),
user_id: authorized_user.id.to_string(),

View File

@@ -1,5 +1,5 @@
use async_trait::async_trait;
use gamenight_api_client_rs::{apis::{configuration::Configuration, default_api::get_token}, models};
use gamenight_api_client_rs::models;
use inquire::{Password, Text};
use super::*;
@@ -18,7 +18,6 @@ impl Login {
#[async_trait]
impl<'a> Flow<'a> for Login {
async fn run(&self, state: &'a mut GamenightState) -> FlowResult<'a> {
let configuration = Configuration::new();
let username = Text::new("What is your login?").prompt()?;
let password = Password::new("what is your password?")
@@ -27,11 +26,11 @@ impl<'a> Flow<'a> for Login {
let login = models::Login::new(username, password);
let result = get_token(&configuration, Some(login)).await?;
let result = state.api.get_token(Some(login)).await?;
clear_screen::clear();
if let Some(token) = result.jwt_token {
state.api_configuration.bearer_access_token = Some(token);
state.set_bearer_access_token(Some(token));
Ok((FlowOutcome::Successful, state))
} else {
Err(FlowError{error: "Unexpected response".to_string()})

View File

@@ -1,5 +1,6 @@
use gamenight_api_client_rs::apis::default_api::{DefaultApi, DefaultApiClient};
use std::{fmt::Display, num::ParseIntError};
use std::sync::{Arc, MutexGuard, PoisonError};
use async_trait::async_trait;
use chrono::ParseError;
use gamenight_api_client_rs::apis::{configuration::Configuration, Error};
@@ -38,10 +39,12 @@ mod location_authorize;
mod remove_game;
pub struct GamenightState {
api_configuration: Configuration,
api_configuration: Arc<Configuration>,
api: Box<dyn DefaultApi>,
gamenight_configuration: Config,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Claims {
exp: i64,
@@ -50,8 +53,10 @@ pub struct Claims {
impl GamenightState {
pub fn new() -> Self{
let config = Arc::new(Configuration::new());
Self {
api_configuration: Configuration::new(),
api_configuration: config.clone(),
api: Box::new(DefaultApiClient::new(config.clone())),
gamenight_configuration: Config::new()
}
}
@@ -67,6 +72,22 @@ impl GamenightState {
Ok(claims.uid)
}
pub fn set_bearer_access_token(&mut self, token: Option<String>) -> &mut Self {
let mut config : Configuration = Arc::<Configuration>::into_inner(self.api_configuration.clone()).unwrap_or(Configuration::new());
config.bearer_access_token = token;
self.api_configuration = Arc::from(config);
self.api = Box::new(DefaultApiClient::new(self.api_configuration.clone()));
self
}
pub fn set_api_base_path(&mut self, base_path: String) -> &mut Self {
let mut config : Configuration = Arc::<Configuration>::into_inner(self.api_configuration.clone()).unwrap_or(Configuration::new());
config.base_path = base_path;
self.api_configuration = Arc::from(config);
self.api = Box::new(DefaultApiClient::new(self.api_configuration.clone()));
self
}
}
impl Default for GamenightState {
@@ -136,6 +157,30 @@ impl From<jsonwebtoken::errors::Error> for FlowError {
}
}
impl From<PoisonError<&mut Configuration>> for FlowError {
fn from(value: PoisonError<&mut Configuration>) -> Self {
Self {
error: value.to_string()
}
}
}
impl From<PoisonError<Configuration>> for FlowError {
fn from(value: PoisonError<Configuration>) -> Self {
Self {
error: value.to_string()
}
}
}
impl From<PoisonError<std::sync::MutexGuard<'_, Configuration>>> for FlowError {
fn from(value: PoisonError<MutexGuard<'_, Configuration>>) -> Self {
Self {
error: value.to_string()
}
}
}
#[derive(PartialEq)]
pub enum FlowOutcome {
Successful,

View File

@@ -3,9 +3,7 @@ use std::fmt::Display;
use super::{Flow, FlowError, FlowOutcome, FlowResult, GamenightState};
use crate::domain::location_select_data::LocationSelectData;
use async_trait::async_trait;
use gamenight_api_client_rs::apis::default_api::locations_get;
use gamenight_api_client_rs::models::OwnGameRequestBody;
use gamenight_api_client_rs::apis::default_api::own_post;
use inquire::{Confirm, Select};
use uuid::Uuid;
@@ -35,13 +33,13 @@ impl<'a> Flow<'a> for Own {
if owned {
if let Some(willing_to_travel) = Confirm::new("Are you willing to travel with this game?").prompt_skippable()? {
if !willing_to_travel {
let locations = locations_get(&state.api_configuration).await?.iter().map(|x| { x.try_into() }).collect::<Result<Vec<LocationSelectData>, FlowError>>()?;
let locations = state.api.locations_get().await?.iter().map(|x| { x.try_into() }).collect::<Result<Vec<LocationSelectData>, FlowError>>()?;
if let Some(location) = Select::new("What location can this game be played?", locations).prompt_skippable()? {
own_game_request.location_id = Some(location.id.to_string());
}
}
}
let _ = own_post(&state.api_configuration, Some(own_game_request)).await?;
let _ = state.api.own_post(Some(own_game_request)).await?;
}
}

View File

@@ -25,7 +25,7 @@ impl<'a> Flow<'a> for RemoveGame {
let req = GameId {
game_id: self.game.id.to_string()
};
gamenight_api_client_rs::apis::default_api::game_delete(&state.api_configuration, Some(req)).await?;
state.api.game_delete(Some(req)).await?;
//Hack to return to right stack item, skipping detail view that doesn't exist.
Ok((FlowOutcome::Abort, state))
}

View File

@@ -1,7 +1,7 @@
use std::fmt::Display;
use async_trait::async_trait;
use gamenight_api_client_rs::{apis::default_api::rename_game_post, models::RenameGameRequestBody};
use gamenight_api_client_rs::models::RenameGameRequestBody;
use inquire::Text;
use crate::domain::game::Game;
@@ -32,7 +32,7 @@ impl<'a> Flow<'a> for RenameGame {
id: self.game.id.to_string(),
name
};
rename_game_post(&state.api_configuration, Some(req)).await?;
state.api.rename_game_post(Some(req)).await?;
return Ok((FlowOutcome::Successful, state))
}

View File

@@ -1,5 +1,5 @@
use gamenight_api_client_rs::{apis::default_api::{game_get, owned_games_get}, models::{GameId, UserId}};
use gamenight_api_client_rs::models::{GameId, UserId};
use inquire::Select;
use uuid::Uuid;
@@ -24,15 +24,15 @@ impl ViewGame {
impl<'a> Flow<'a> for ViewGame {
async fn run(&self, state: &'a mut GamenightState) -> FlowResult<'a> {
let game_id = GameId{ game_id: self.game.id.to_string() };
let game: Game = game_get(&state.api_configuration, Some(game_id)).await?.into();
let game: Game = state.api.game_get(Some(game_id)).await?.into();
println!("{}", game);
let my_uid = state.get_user_id()?;
let request = UserId{ user_id: my_uid.to_string() };
let owned_games: Vec<Uuid> = owned_games_get(&state.api_configuration, Some(request)).await?
let owned_games: Vec<Uuid> = state.api.owned_games_get(Some(request)).await?
.iter().map(|x| -> Result<Uuid, FlowError> {
Ok(Uuid::parse_str(x)?)
Ok(Uuid::parse_str(&x.game_id)?)
}).collect::<Result<Vec<Uuid>, FlowError>>()?;
let own_or_disown: Box<dyn Flow<'a> + Send> =

View File

@@ -1,13 +1,11 @@
use crate::domain::gamenight::Gamenight;
use std::collections::HashMap;
use futures::future::join_all;
use gamenight_api_client_rs::{apis::default_api::{game_get, owned_games_get, participants_get, user_get, GameGetError}, models::{self, GameId, GamenightId, UserId}};
use gamenight_api_client_rs::models::{GameId, GamenightId, LocationId, OwnedGame, UserId};
use inquire::Select;
use crate::{domain::{game::Game, gamenight_select_data::GamenightSelectData, owned_games::OwnedGames, participants::Participants}, flows::{exit::Exit, join::Join, leave::Leave}};
use super::*;
use crate::{domain::{gamenight_select_data::GamenightSelectData, owned_games::OwnedGames, participants::Participants}, flows::{exit::Exit, join::Join, leave::Leave}};
#[derive(Clone)]
pub struct ViewGamenight {
@@ -30,25 +28,35 @@ impl<'a> Flow<'a> for ViewGamenight {
id: self.gamenight_select_data.id,
start_time: self.gamenight_select_data.start_time,
name: self.gamenight_select_data.name.clone(),
organizer: user_get(&state.api_configuration, Some(UserId{user_id: self.gamenight_select_data.owner_id.to_string()})).await?.try_into()?,
location: None,
organizer: state.api.user_get(Some(UserId{user_id: self.gamenight_select_data.owner_id.to_string()})).await?.try_into()?,
participants: Participants(vec![]),
owned_games: OwnedGames(HashMap::new())
};
let participants = participants_get(&state.api_configuration, Some(GamenightId{gamenight_id: gamenight.id.to_string()})).await?;
gamenight.location = match self.gamenight_select_data.location_id {
None => None,
Some(l) => Some(state.api.location_get(Some(LocationId{location_id: l.to_string()})).await?.try_into()?)
};
let participants = state.api.participants_get(Some(GamenightId{gamenight_id: gamenight.id.to_string()})).await?;
for participant in participants.participants.iter() {
gamenight.participants.0.push(user_get(&state.api_configuration, Some(UserId{user_id: participant.clone()})).await?.try_into()?);
gamenight.participants.0.push(state.api.user_get(Some(UserId{user_id: participant.clone()})).await?.try_into()?);
}
for user in &gamenight.participants.0 {
let request = UserId{ user_id: user.id.to_string() };
let games: Vec<Game> = join_all(owned_games_get(&state.api_configuration, Some(request)).await?
.iter().map(async |game_id| -> Result<models::Game, Error<GameGetError>> {
let request = GameId{ game_id: game_id.clone() };
game_get(&state.api_configuration, Some(request)).await
})).await.into_iter().collect::<Result<Vec<models::Game>, Error<GameGetError>>>()?.into_iter().map(Into::into).collect();
gamenight.owned_games.0.insert(user.username.clone(), games);
let owned_games_refs: Vec<OwnedGame> = state.api.owned_games_get(Some(request)).await?;
let mut owned_games = vec![];
for owned_games_ref in owned_games_refs {
let game = state.api.game_get(Some(GameId{ game_id: owned_games_ref.game_id.clone()})).await?.into();
let location = match owned_games_ref.location_id {
None => None,
Some(x) => Some(state.api.location_get(Some(LocationId{location_id: x})).await?.try_into()?)
};
owned_games.push((game, location));
}
gamenight.owned_games.0.insert(user.username.clone(), owned_games);
}
println!("{}", gamenight);

View File

@@ -6,9 +6,9 @@ use flows::{main::Main, Flow, GamenightState};
#[tokio::main]
async fn main() {
let mut state = GamenightState::new();
let mainflow = Main::new();
let main_flow = Main::new();
clear_screen::clear();
if let Err(x) = mainflow.run(&mut state).await {
if let Err(x) = main_flow.run(&mut state).await {
println!("{}", x.error);
}
}

View File

@@ -33,9 +33,9 @@ pub fn disown_game(conn: &mut PgConnection, owned_game: OwnedGame) -> Result<usi
.execute(conn)?)
}
pub fn owned_games(conn: &mut PgConnection, uuid: Uuid) -> Result<Vec<Uuid>, DatabaseError> {
pub fn owned_games(conn: &mut PgConnection, uuid: Uuid) -> Result<Vec<(Uuid, Option<Uuid>)>, DatabaseError> {
Ok(owned_game::table
.select(owned_game::game_id)
.select((owned_game::game_id, owned_game::location_id))
.filter(owned_game::user_id.eq(uuid))
.get_results(conn)?)
}