Added Location and location ownership/rights to gamenight.
This commit is contained in:
828
gamenight-cli/Cargo.lock
generated
828
gamenight-cli/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@ edition = "2024"
|
||||
[dependencies]
|
||||
gamenight-api-client-rs = { path = "../gamenight-api-client-rs" }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
inquire = { version = "0.7.5", features = ["date"] }
|
||||
inquire = { version = "0.7.5", features = ["date", "editor"] }
|
||||
async-trait = "0.1"
|
||||
dyn-clone = "1.0"
|
||||
chrono = "0.4"
|
||||
|
||||
@@ -42,6 +42,9 @@ pub struct Config {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(default)]
|
||||
pub last_instance: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(default)]
|
||||
pub editor: Option<String>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
@@ -57,7 +60,8 @@ impl Config {
|
||||
pub fn new() -> Config {
|
||||
Config {
|
||||
instances: vec![],
|
||||
last_instance: None
|
||||
last_instance: None,
|
||||
editor: None
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
31
gamenight-cli/src/domain/location.rs
Normal file
31
gamenight-cli/src/domain/location.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use gamenight_api_client_rs::models;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Location {
|
||||
pub id: Uuid,
|
||||
pub name: String,
|
||||
pub address: Option<String>,
|
||||
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 Display for Location {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, r#"name: {}
|
||||
address: {}
|
||||
note: {}"#, &self.name, &<std::option::Option<std::string::String> as Clone>::clone(&self.address).unwrap_or_default(), &<std::option::Option<std::string::String> as Clone>::clone(&self.note).unwrap_or_default())
|
||||
}
|
||||
}
|
||||
@@ -3,4 +3,5 @@ pub mod user;
|
||||
pub mod config;
|
||||
pub mod participants;
|
||||
pub mod game;
|
||||
pub mod owned_games;
|
||||
pub mod owned_games;
|
||||
pub mod location;
|
||||
65
gamenight-cli/src/flows/add_location.rs
Normal file
65
gamenight-cli/src/flows/add_location.rs
Normal file
@@ -0,0 +1,65 @@
|
||||
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 inquire::{Editor, Text};
|
||||
|
||||
|
||||
use super::*;
|
||||
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AddLocation {
|
||||
}
|
||||
|
||||
impl AddLocation {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<'a> Flow<'a> for AddLocation {
|
||||
async fn run(&self, state: &'a mut GamenightState) -> FlowResult<'a> {
|
||||
if let Some(name) = Text::new("What is the name of the location you want to add?").prompt_skippable()? {
|
||||
|
||||
let address;
|
||||
let note;
|
||||
{
|
||||
address = Text::new("Optional: What is the address?").prompt_skippable()?;
|
||||
|
||||
let mut editor_prompt = Editor::new("What is the name of the location you want to add?");
|
||||
|
||||
let editor_command;
|
||||
if let Some(editor) = &state.gamenight_configuration.editor {
|
||||
editor_command = editor.clone();
|
||||
editor_prompt = editor_prompt.with_editor_command(OsStr::new(&editor_command))
|
||||
}
|
||||
note = editor_prompt.prompt_skippable()?;
|
||||
}
|
||||
|
||||
let add_location_request = AddLocationRequestBody {
|
||||
name,
|
||||
address,
|
||||
note
|
||||
};
|
||||
|
||||
let location_id = location_post(&state.api_configuration, Some(add_location_request)).await?;
|
||||
|
||||
let add_authorize_request = AuthorizeLocationRequestBody {
|
||||
location_id: location_id.location_id.to_string(),
|
||||
user_id: state.get_user_id()?.to_string(),
|
||||
op: Grant
|
||||
};
|
||||
|
||||
location_authorize_post(&state.api_configuration, Some(add_authorize_request)).await?;
|
||||
}
|
||||
Ok((FlowOutcome::Cancelled, state))
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for AddLocation {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Add Location")
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
use inquire::{ui::RenderConfig, Select};
|
||||
|
||||
use crate::flows::{add_gamenight::AddGamenight, exit::Exit, games::Games, list_gamenights::ListGamenights};
|
||||
use crate::flows::{add_gamenight::AddGamenight, exit::Exit, games::Games, list_gamenights::ListGamenights, locations::Locations};
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -27,6 +27,7 @@ impl<'a> Flow<'a> for GamenightMenu {
|
||||
Box::new(ListGamenights::new()),
|
||||
Box::new(AddGamenight::new()),
|
||||
Box::new(Games::new()),
|
||||
Box::new(Locations::new()),
|
||||
Box::new(Exit::new())
|
||||
];
|
||||
|
||||
|
||||
49
gamenight-cli/src/flows/list_locations.rs
Normal file
49
gamenight-cli/src/flows/list_locations.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
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};
|
||||
|
||||
use super::*;
|
||||
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ListLocations {
|
||||
}
|
||||
|
||||
impl ListLocations {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
#[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 mut flows = locations.into_iter().map(|location| -> Box<dyn Flow + Send> {
|
||||
Box::new(ViewLocation::new(location.into()))
|
||||
}).collect::<Vec::<Box::<dyn Flow + Send>>>();
|
||||
|
||||
flows.push(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,
|
||||
..Default::default()
|
||||
})
|
||||
.prompt_skippable()?;
|
||||
|
||||
self.continue_choice(state, &choice).await
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ListLocations {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "List all locations")
|
||||
}
|
||||
}
|
||||
101
gamenight-cli/src/flows/location_authorize.rs
Normal file
101
gamenight-cli/src/flows/location_authorize.rs
Normal file
@@ -0,0 +1,101 @@
|
||||
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 inquire::MultiSelect;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::flows::FlowError;
|
||||
|
||||
use super::{Flow, FlowOutcome, FlowResult, GamenightState};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct LocationAuthorize {
|
||||
location_id: Uuid
|
||||
}
|
||||
|
||||
impl LocationAuthorize {
|
||||
pub fn new(location_id: Uuid) -> Self {
|
||||
Self {
|
||||
location_id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct AuthorizeMultiSelectStruct<'a> {
|
||||
id: Uuid,
|
||||
username: &'a String
|
||||
}
|
||||
|
||||
impl<'a> Display for AuthorizeMultiSelectStruct<'a> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.username)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a User> for AuthorizeMultiSelectStruct<'a> {
|
||||
type Error = FlowError;
|
||||
|
||||
fn try_from(value: &'a User) -> Result<Self, Self::Error> {
|
||||
Ok(AuthorizeMultiSelectStruct{
|
||||
id: Uuid::parse_str(&value.id)?,
|
||||
username: &value.username
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[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 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 : 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>>()?;
|
||||
|
||||
let (authorized_indices, authorized_users) : &(Vec<usize>, Vec<&User>) = &users.iter().enumerate().filter(|t| {
|
||||
authorized_user_ids.contains(&t.1.id)
|
||||
}).unzip();
|
||||
|
||||
let selections = MultiSelect::new("Which users should be able to host gamenights in this location?", options)
|
||||
.with_default(&authorized_indices[..])
|
||||
.prompt_skippable()?;
|
||||
|
||||
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(
|
||||
AuthorizeLocationRequestBody {
|
||||
location_id: self.location_id.to_string(),
|
||||
user_id: selection.id.to_string(),
|
||||
op: gamenight_api_client_rs::models::authorize_location_request_body::Op::Grant
|
||||
}
|
||||
)).await?
|
||||
}
|
||||
}
|
||||
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(
|
||||
AuthorizeLocationRequestBody {
|
||||
location_id: self.location_id.to_string(),
|
||||
user_id: authorized_user.id.to_string(),
|
||||
op: gamenight_api_client_rs::models::authorize_location_request_body::Op::Revoke
|
||||
}
|
||||
)).await?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clear_screen::clear();
|
||||
Ok((FlowOutcome::Successful, state))
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for LocationAuthorize {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Authorize users for location")
|
||||
}
|
||||
}
|
||||
46
gamenight-cli/src/flows/locations.rs
Normal file
46
gamenight-cli/src/flows/locations.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use inquire::{ui::RenderConfig, Select};
|
||||
|
||||
use crate::flows::{add_location::AddLocation, exit::Exit, list_locations::ListLocations};
|
||||
|
||||
use super::*;
|
||||
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Locations {
|
||||
}
|
||||
|
||||
impl Locations {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<'a> Flow<'a> for Locations {
|
||||
async fn run(&self, state: &'a mut GamenightState) -> FlowResult<'a> {
|
||||
let flows: Vec<Box<dyn Flow + Send>> = vec![
|
||||
Box::new(ListLocations::new()),
|
||||
Box::new(AddLocation::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,
|
||||
..Default::default()
|
||||
})
|
||||
.prompt_skippable()?;
|
||||
|
||||
self.continue_choice(state, &choice).await
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Locations {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Locations")
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,11 @@ mod view_game;
|
||||
mod rename_game;
|
||||
mod own;
|
||||
mod disown;
|
||||
mod locations;
|
||||
mod list_locations;
|
||||
mod view_location;
|
||||
mod add_location;
|
||||
mod location_authorize;
|
||||
|
||||
pub struct GamenightState {
|
||||
api_configuration: Configuration,
|
||||
@@ -122,6 +127,14 @@ impl From<ParseIntError> for FlowError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<jsonwebtoken::errors::Error> for FlowError {
|
||||
fn from(value: jsonwebtoken::errors::Error) -> Self {
|
||||
Self {
|
||||
error: value.to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum FlowOutcome {
|
||||
Successful,
|
||||
|
||||
@@ -23,14 +23,6 @@ impl ViewGamenight {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<jsonwebtoken::errors::Error> for FlowError {
|
||||
fn from(value: jsonwebtoken::errors::Error) -> Self {
|
||||
Self {
|
||||
error: value.to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<'a> Flow<'a> for ViewGamenight {
|
||||
async fn run(&self, state: &'a mut GamenightState) -> FlowResult<'a> {
|
||||
|
||||
44
gamenight-cli/src/flows/view_location.rs
Normal file
44
gamenight-cli/src/flows/view_location.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
|
||||
use inquire::Select;
|
||||
|
||||
use crate::{domain::location::Location, flows::{exit::Exit, location_authorize::LocationAuthorize}};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ViewLocation {
|
||||
location: Location
|
||||
}
|
||||
|
||||
impl ViewLocation {
|
||||
pub fn new(location: Location) -> Self {
|
||||
Self {
|
||||
location
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<'a> Flow<'a> for ViewLocation {
|
||||
async fn run(&self, state: &'a mut GamenightState) -> FlowResult<'a> {
|
||||
|
||||
println!("{}", self.location);
|
||||
|
||||
let options: Vec<Box<dyn Flow<'a> + Send>> = vec![
|
||||
Box::new(LocationAuthorize::new(self.location.id)),
|
||||
Box::new(Exit::new())
|
||||
];
|
||||
|
||||
let choice = Select::new("What do you want to do:", options)
|
||||
.prompt_skippable()?;
|
||||
|
||||
self.continue_choice(state, &choice).await
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ViewLocation {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.location.name)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user