Started working on a cli frontend.

This commit is contained in:
2025-04-23 20:27:06 +02:00
parent 02913c7b52
commit db25dc0aed
49 changed files with 3838 additions and 59 deletions

1
gamenight-cli/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
target

1933
gamenight-cli/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

10
gamenight-cli/Cargo.toml Normal file
View File

@@ -0,0 +1,10 @@
[package]
name = "gamenight-cli"
version = "0.1.0"
edition = "2024"
[dependencies]
gamenight-api-client-rs = { path = "../gamenight-api-client-rs" }
tokio = { version = "1", features = ["full"] }
inquire = "0.7.5"
async-trait = "0.1"

View File

@@ -0,0 +1,24 @@
use super::*;
pub struct Abort {
}
impl Abort {
pub fn new() -> Self {
Self{}
}
}
#[async_trait]
impl<'a> Flow<'a> for Abort {
async fn run(&self, state: &'a mut GamenightState) -> FlowResult<'a> {
Ok((FlowOutcome::Abort, state))
}
}
impl Display for Abort {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Abort")
}
}

View File

@@ -0,0 +1,30 @@
use gamenight_api_client_rs::apis::default_api::get_gamenights;
use super::*;
pub struct ListGamenights {
}
impl ListGamenights {
pub fn new() -> Self {
Self {}
}
}
#[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?;
println!("{:?}", response);
Ok((FlowOutcome::Successful, state))
}
}
impl Display for ListGamenights {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "List all gamenights")
}
}

View File

@@ -0,0 +1,43 @@
use async_trait::async_trait;
use gamenight_api_client_rs::{apis::{configuration::Configuration, default_api::get_token}, models};
use inquire::{Password, Text};
use super::*;
pub struct Login {
}
impl Login {
pub fn new() -> Self {
Self {}
}
}
#[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?")
.without_confirmation()
.prompt()?;
let login = models::Login::new(username, password);
let result = get_token(&configuration, Some(login)).await?;
if let Some(token) = result.jwt_token {
state.configuration.bearer_access_token = Some(token);
Ok((FlowOutcome::Successful, state))
} else {
Err(FlowError{error: "Unexpected response".to_string()})
}
}
}
impl Display for Login {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Login")
}
}

View File

@@ -0,0 +1,35 @@
use super::{abort::Abort, list_gamenights::ListGamenights, login::Login, main_menu::MainMenu, *};
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(Abort::new()));
Self {
login: Box::new(Login::new()),
main_menu: main_menu
}
}
}
#[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))
}
}
impl Display for Main {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "main")
}
}

View File

@@ -0,0 +1,53 @@
use inquire::{ui::RenderConfig, Select};
use super::*;
pub struct MainMenu {
pub menu: Vec<Box<dyn for<'a> Flow<'a> + Send>>
}
impl MainMenu {
pub fn new() -> Self {
MainMenu {
menu: vec![]
}
}
}
unsafe impl Send for MainMenu {
}
#[async_trait]
impl<'a> Flow<'a> for MainMenu {
async fn run(&self, state: &'a mut GamenightState) -> FlowResult<'a> {
let choice = Select::new("What would you like to do?", self.menu)
.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()?;
if let Some(choice) = choice {
let (outcome, new_state) = choice.run(state).await?;
if outcome == FlowOutcome::Abort {
Ok((outcome, new_state))
}
else {
self.run(new_state).await
}
}
else {
return Ok((FlowOutcome::Abort, state))
}
}
}
impl Display for MainMenu {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Main menu")
}
}

View File

@@ -0,0 +1,59 @@
use std::fmt::Display;
use async_trait::async_trait;
use gamenight_api_client_rs::apis::configuration::Configuration;
use inquire::InquireError;
pub mod main;
pub mod login;
mod main_menu;
mod abort;
mod list_gamenights;
pub struct GamenightState {
configuration: Configuration,
}
impl GamenightState {
pub fn new() -> Self{
Self {
configuration: Configuration::new()
}
}
}
#[derive(Debug)]
pub struct FlowError {
pub error: String
}
impl From<InquireError> for FlowError {
fn from(value: InquireError) -> Self {
Self {
error: value.to_string()
}
}
}
impl<T> From<gamenight_api_client_rs::apis::Error<T>> for FlowError {
fn from(value: gamenight_api_client_rs::apis::Error<T>) -> Self {
Self {
error: value.to_string()
}
}
}
#[derive(PartialEq)]
pub enum FlowOutcome {
Successful,
Bool(bool),
String(String),
Abort
}
type FlowResult<'a> = Result<(FlowOutcome, &'a mut GamenightState), FlowError>;
#[async_trait]
pub trait Flow<'a>: Sync + Display {
async fn run(&self, state: &'a mut GamenightState) -> FlowResult<'a>;
}

14
gamenight-cli/src/main.rs Normal file
View File

@@ -0,0 +1,14 @@
pub mod flows;
use flows::{main::Main, Flow, GamenightState};
#[tokio::main]
async fn main() {
let mut state = GamenightState::new();
let mainflow = Main::new();
if let Err(x) = mainflow.run(&mut state).await {
println!("{}", x.error);
}
}