Adds Adding of Games to the system.

This commit is contained in:
Dennis Brentjes 2025-06-27 22:08:23 +02:00
parent 3f51d52edf
commit 28f7306d57
40 changed files with 792 additions and 130 deletions

View File

@ -2,6 +2,7 @@ use std::{fs::{exists, read_dir, remove_dir_all, File}, io::Write, process::Comm
fn main() { fn main() {
println!("cargo::rerun-if-changed=gamenight-api.yaml");
if exists("src/models").unwrap() { if exists("src/models").unwrap() {
remove_dir_all("src/models").unwrap(); remove_dir_all("src/models").unwrap();

View File

@ -38,7 +38,6 @@ paths:
parameters: [] parameters: []
security: security:
- JWT-Auth: [] - JWT-Auth: []
/user: /user:
post: post:
summary: '' summary: ''
@ -146,7 +145,7 @@ paths:
post: post:
responses: responses:
'200': '200':
description: OK description: "OK"
'401': '401':
$ref: '#/components/responses/FailureResponse' $ref: '#/components/responses/FailureResponse'
'422': '422':
@ -155,6 +154,42 @@ paths:
$ref: '#/components/requestBodies/LeaveGamenight' $ref: '#/components/requestBodies/LeaveGamenight'
security: security:
- JWT-Auth: [] - JWT-Auth: []
/games:
get:
responses:
'200':
$ref: '#/components/responses/GamesResponse'
'401':
$ref: '#/components/responses/FailureResponse'
'422':
$ref: '#/components/responses/FailureResponse'
security:
- JWT-Auth: []
/game:
get:
responses:
'200':
$ref: '#/components/responses/GameResponse'
'401':
$ref: '#/components/responses/FailureResponse'
'422':
$ref: '#/components/responses/FailureResponse'
requestBody:
$ref: '#/components/requestBodies/GetGameRequest'
security:
- JWT-Auth: []
post:
responses:
'200':
description: "OK"
'401':
$ref: '#/components/responses/FailureResponse'
'422':
$ref: '#/components/responses/FailureResponse'
requestBody:
$ref: '#/components/requestBodies/AddGameRequest'
security:
- JWT-Auth: []
components: components:
schemas: schemas:
@ -256,6 +291,14 @@ components:
type: string type: string
required: required:
- gamenight_id - gamenight_id
GameId:
title: GameId
type: object
properties:
game_id:
type: string
required:
- game_id
GetGamenightRequestBody: GetGamenightRequestBody:
type: object type: object
properties: properties:
@ -273,6 +316,24 @@ components:
required: required:
- id - id
- username - username
Game:
type: object
properties:
id:
type: string
name:
type: string
required:
- id
- name
AddGameRequestBody:
type: object
properties:
name:
type: string
required:
- name
requestBodies: requestBodies:
LoginRequest: LoginRequest:
content: content:
@ -314,6 +375,16 @@ components:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/GamenightId' $ref: '#/components/schemas/GamenightId'
GetGameRequest:
content:
application/json:
schema:
$ref: '#/components/schemas/GameId'
AddGameRequest:
content:
application/json:
schema:
$ref: '#/components/schemas/AddGameRequestBody'
responses: responses:
TokenResponse: TokenResponse:
description: Example response description: Example response
@ -353,6 +424,20 @@ components:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/User' $ref: '#/components/schemas/User'
GamesResponse:
description: A list of Games in this gamenight instance.
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Game'
GameResponse:
description: A game.
content:
application/json:
schema:
$ref: '#/components/schemas/Game'
securitySchemes: securitySchemes:
JWT-Auth: JWT-Auth:
type: http type: http

View File

@ -48,6 +48,8 @@ async fn main() -> std::io::Result<()> {
.service(post_join_gamenight) .service(post_join_gamenight)
.service(post_leave_gamenight) .service(post_leave_gamenight)
.service(get_get_participants) .service(get_get_participants)
.service(get_games)
.service(post_game)
}) })
.bind(("::1", 8080))? .bind(("::1", 8080))?
.run() .run()

View File

@ -0,0 +1,39 @@
use actix_web::{get, http::header::ContentType, post, web, HttpResponse, Responder};
use gamenight_database::{game::insert_game, DbPool, GetConnection};
use uuid::Uuid;
use crate::{models::{add_game_request_body::AddGameRequestBody, game::Game}, request::{authorization::AuthUser, error::ApiError}};
#[get("/games")]
pub async fn get_games(pool: web::Data<DbPool>, _user: AuthUser) -> Result<impl Responder, ApiError> {
let mut conn = pool.get_conn();
let games: Vec<gamenight_database::game::Game> = gamenight_database::games(&mut conn)?;
let model: Vec<Game> = games.iter().map(|x| {
Game {
id: x.id.to_string(),
name: x.name.clone(),
}}
).collect();
Ok(HttpResponse::Ok()
.content_type(ContentType::json())
.body(serde_json::to_string(&model)?)
)
}
impl From<AddGameRequestBody> for gamenight_database::game::Game {
fn from(value: AddGameRequestBody) -> Self {
Self {
id: Uuid::new_v4(),
name: value.name
}
}
}
#[post("/game")]
pub async fn post_game(pool: web::Data<DbPool>, _user: AuthUser, game_data: web::Json<AddGameRequestBody>) -> Result<impl Responder, ApiError> {
let mut conn = pool.get_conn();
insert_game(&mut conn, game_data.0.into())?;
Ok(HttpResponse::Ok())
}

View File

@ -5,6 +5,7 @@ mod error;
mod authorization; mod authorization;
mod join_gamenight; mod join_gamenight;
mod participant_handlers; mod participant_handlers;
mod game;
pub use user_handlers::login; pub use user_handlers::login;
pub use user_handlers::refresh; pub use user_handlers::refresh;
@ -17,3 +18,5 @@ pub use user_handlers::get_user_unauthenticated;
pub use join_gamenight::post_join_gamenight; pub use join_gamenight::post_join_gamenight;
pub use join_gamenight::post_leave_gamenight; pub use join_gamenight::post_leave_gamenight;
pub use participant_handlers::get_get_participants; pub use participant_handlers::get_get_participants;
pub use game::get_games;
pub use game::post_game;

View File

@ -2,9 +2,12 @@
.travis.yml .travis.yml
Cargo.toml Cargo.toml
README.md README.md
docs/AddGameRequestBody.md
docs/AddGamenightRequestBody.md docs/AddGamenightRequestBody.md
docs/DefaultApi.md docs/DefaultApi.md
docs/Failure.md docs/Failure.md
docs/Game.md
docs/GameId.md
docs/Gamenight.md docs/Gamenight.md
docs/GamenightId.md docs/GamenightId.md
docs/GetGamenightRequestBody.md docs/GetGamenightRequestBody.md
@ -19,8 +22,11 @@ src/apis/configuration.rs
src/apis/default_api.rs src/apis/default_api.rs
src/apis/mod.rs src/apis/mod.rs
src/lib.rs src/lib.rs
src/models/add_game_request_body.rs
src/models/add_gamenight_request_body.rs src/models/add_gamenight_request_body.rs
src/models/failure.rs src/models/failure.rs
src/models/game.rs
src/models/game_id.rs
src/models/gamenight.rs src/models/gamenight.rs
src/models/gamenight_id.rs src/models/gamenight_id.rs
src/models/get_gamenight_request_body.rs src/models/get_gamenight_request_body.rs

View File

@ -27,6 +27,9 @@ All URIs are relative to *http://localhost:8080*
Class | Method | HTTP request | Description Class | Method | HTTP request | Description
------------ | ------------- | ------------- | ------------- ------------ | ------------- | ------------- | -------------
*DefaultApi* | [**game_get**](docs/DefaultApi.md#game_get) | **GET** /game |
*DefaultApi* | [**game_post**](docs/DefaultApi.md#game_post) | **POST** /game |
*DefaultApi* | [**games_get**](docs/DefaultApi.md#games_get) | **GET** /games |
*DefaultApi* | [**get_gamenight**](docs/DefaultApi.md#get_gamenight) | **GET** /gamenight | *DefaultApi* | [**get_gamenight**](docs/DefaultApi.md#get_gamenight) | **GET** /gamenight |
*DefaultApi* | [**get_gamenights**](docs/DefaultApi.md#get_gamenights) | **GET** /gamenights | Get a all gamenights *DefaultApi* | [**get_gamenights**](docs/DefaultApi.md#get_gamenights) | **GET** /gamenights | Get a all gamenights
*DefaultApi* | [**get_token**](docs/DefaultApi.md#get_token) | **GET** /token | *DefaultApi* | [**get_token**](docs/DefaultApi.md#get_token) | **GET** /token |
@ -41,8 +44,11 @@ Class | Method | HTTP request | Description
## Documentation For Models ## Documentation For Models
- [AddGameRequestBody](docs/AddGameRequestBody.md)
- [AddGamenightRequestBody](docs/AddGamenightRequestBody.md) - [AddGamenightRequestBody](docs/AddGamenightRequestBody.md)
- [Failure](docs/Failure.md) - [Failure](docs/Failure.md)
- [Game](docs/Game.md)
- [GameId](docs/GameId.md)
- [Gamenight](docs/Gamenight.md) - [Gamenight](docs/Gamenight.md)
- [GamenightId](docs/GamenightId.md) - [GamenightId](docs/GamenightId.md)
- [GetGamenightRequestBody](docs/GetGamenightRequestBody.md) - [GetGamenightRequestBody](docs/GetGamenightRequestBody.md)

View File

@ -1,6 +1,7 @@
use std::process::Command; use std::process::Command;
fn main() { fn main() {
println!("cargo::rerun-if-changed=../backend-actix/gamenight-api.yaml");
let _ = let _ =
Command::new("openapi-generator") 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,modelPackage=gamenight_model,apiPackage=gamenight_api,packageName=gamenight-api-client-rs,packageVersion=0.1.0"])

View File

@ -0,0 +1,11 @@
# AddGame
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**name** | **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)

View File

@ -0,0 +1,11 @@
# AddGameRequestBody
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**name** | **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)

View File

@ -4,6 +4,9 @@ All URIs are relative to *http://localhost:8080*
Method | HTTP request | Description Method | HTTP request | Description
------------- | ------------- | ------------- ------------- | ------------- | -------------
[**game_get**](DefaultApi.md#game_get) | **GET** /game |
[**game_post**](DefaultApi.md#game_post) | **POST** /game |
[**games_get**](DefaultApi.md#games_get) | **GET** /games |
[**get_gamenight**](DefaultApi.md#get_gamenight) | **GET** /gamenight | [**get_gamenight**](DefaultApi.md#get_gamenight) | **GET** /gamenight |
[**get_gamenights**](DefaultApi.md#get_gamenights) | **GET** /gamenights | Get a all gamenights [**get_gamenights**](DefaultApi.md#get_gamenights) | **GET** /gamenights | Get a all gamenights
[**get_token**](DefaultApi.md#get_token) | **GET** /token | [**get_token**](DefaultApi.md#get_token) | **GET** /token |
@ -17,6 +20,87 @@ Method | HTTP request | Description
## game_get
> models::Game game_get(game_id)
### Parameters
Name | Type | Description | Required | Notes
------------- | ------------- | ------------- | ------------- | -------------
**game_id** | Option<[**GameId**](GameId.md)> | | |
### Return type
[**models::Game**](Game.md)
### Authorization
[JWT-Auth](../README.md#JWT-Auth)
### HTTP request headers
- **Content-Type**: application/json
- **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)
## game_post
> game_post(add_game_request_body)
### Parameters
Name | Type | Description | Required | Notes
------------- | ------------- | ------------- | ------------- | -------------
**add_game_request_body** | Option<[**AddGameRequestBody**](AddGameRequestBody.md)> | | |
### Return type
(empty response body)
### Authorization
[JWT-Auth](../README.md#JWT-Auth)
### HTTP request headers
- **Content-Type**: application/json
- **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)
## games_get
> Vec<models::Game> games_get()
### Parameters
This endpoint does not need any parameter.
### Return type
[**Vec<models::Game>**](Game.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)
## get_gamenight ## get_gamenight
> models::Gamenight get_gamenight(get_gamenight_request_body) > models::Gamenight get_gamenight(get_gamenight_request_body)

View File

@ -0,0 +1,12 @@
# Game
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**id** | **String** | |
**name** | **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)

View File

@ -0,0 +1,11 @@
# GameId
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**game_id** | **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)

View File

@ -15,6 +15,33 @@ use crate::{apis::ResponseContent, models};
use super::{Error, configuration, ContentType}; use super::{Error, configuration, ContentType};
/// struct for typed errors of method [`game_get`]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum GameGetError {
Status401(models::Failure),
Status422(models::Failure),
UnknownValue(serde_json::Value),
}
/// struct for typed errors of method [`game_post`]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum GamePostError {
Status401(models::Failure),
Status422(models::Failure),
UnknownValue(serde_json::Value),
}
/// struct for typed errors of method [`games_get`]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum GamesGetError {
Status401(models::Failure),
Status422(models::Failure),
UnknownValue(serde_json::Value),
}
/// struct for typed errors of method [`get_gamenight`] /// struct for typed errors of method [`get_gamenight`]
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)] #[serde(untagged)]
@ -104,6 +131,112 @@ pub enum UserGetError {
} }
pub async fn game_get(configuration: &configuration::Configuration, game_id: Option<models::GameId>) -> Result<models::Game, Error<GameGetError>> {
// add a prefix to parameters to efficiently prevent name collisions
let p_game_id = game_id;
let uri_str = format!("{}/game", configuration.base_path);
let mut req_builder = configuration.client.request(reqwest::Method::GET, &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());
};
req_builder = req_builder.json(&p_game_id);
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::Game`"))),
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::Game`")))),
}
} else {
let content = resp.text().await?;
let entity: Option<GameGetError> = serde_json::from_str(&content).ok();
Err(Error::ResponseError(ResponseContent { status, content, entity }))
}
}
pub async fn game_post(configuration: &configuration::Configuration, add_game_request_body: Option<models::AddGameRequestBody>) -> Result<(), Error<GamePostError>> {
// add a prefix to parameters to efficiently prevent name collisions
let p_add_game_request_body = add_game_request_body;
let uri_str = format!("{}/game", 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());
};
req_builder = req_builder.json(&p_add_game_request_body);
let req = req_builder.build()?;
let resp = configuration.client.execute(req).await?;
let status = resp.status();
if !status.is_client_error() && !status.is_server_error() {
Ok(())
} else {
let content = resp.text().await?;
let entity: Option<GamePostError> = serde_json::from_str(&content).ok();
Err(Error::ResponseError(ResponseContent { status, content, entity }))
}
}
pub async fn games_get(configuration: &configuration::Configuration, ) -> Result<Vec<models::Game>, Error<GamesGetError>> {
let uri_str = format!("{}/games", configuration.base_path);
let mut req_builder = configuration.client.request(reqwest::Method::GET, &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 `Vec&lt;models::Game&gt;`"))),
ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `Vec&lt;models::Game&gt;`")))),
}
} else {
let content = resp.text().await?;
let entity: Option<GamesGetError> = serde_json::from_str(&content).ok();
Err(Error::ResponseError(ResponseContent { status, content, entity }))
}
}
pub async fn get_gamenight(configuration: &configuration::Configuration, get_gamenight_request_body: Option<models::GetGamenightRequestBody>) -> Result<models::Gamenight, Error<GetGamenightError>> { pub async fn get_gamenight(configuration: &configuration::Configuration, get_gamenight_request_body: Option<models::GetGamenightRequestBody>) -> Result<models::Gamenight, Error<GetGamenightError>> {
// add a prefix to parameters to efficiently prevent name collisions // add a prefix to parameters to efficiently prevent name collisions
let p_get_gamenight_request_body = get_gamenight_request_body; let p_get_gamenight_request_body = get_gamenight_request_body;

View File

@ -0,0 +1,27 @@
/*
* Gamenight
*
* Api specifaction 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 AddGame {
#[serde(rename = "name")]
pub name: String,
}
impl AddGame {
pub fn new(name: String) -> AddGame {
AddGame {
name,
}
}
}

View File

@ -0,0 +1,27 @@
/*
* Gamenight
*
* Api specifaction 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 AddGameRequestBody {
#[serde(rename = "name")]
pub name: String,
}
impl AddGameRequestBody {
pub fn new(name: String) -> AddGameRequestBody {
AddGameRequestBody {
name,
}
}
}

View File

@ -0,0 +1,30 @@
/*
* Gamenight
*
* Api specifaction 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 Game {
#[serde(rename = "id")]
pub id: String,
#[serde(rename = "name")]
pub name: String,
}
impl Game {
pub fn new(id: String, name: String) -> Game {
Game {
id,
name,
}
}
}

View File

@ -0,0 +1,27 @@
/*
* Gamenight
*
* Api specifaction 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 GameId {
#[serde(rename = "game_id")]
pub game_id: String,
}
impl GameId {
pub fn new(game_id: String) -> GameId {
GameId {
game_id,
}
}
}

View File

@ -1,7 +1,13 @@
pub mod add_game_request_body;
pub use self::add_game_request_body::AddGameRequestBody;
pub mod add_gamenight_request_body; pub mod add_gamenight_request_body;
pub use self::add_gamenight_request_body::AddGamenightRequestBody; pub use self::add_gamenight_request_body::AddGamenightRequestBody;
pub mod failure; pub mod failure;
pub use self::failure::Failure; pub use self::failure::Failure;
pub mod game;
pub use self::game::Game;
pub mod game_id;
pub use self::game_id::GameId;
pub mod gamenight; pub mod gamenight;
pub use self::gamenight::Gamenight; pub use self::gamenight::Gamenight;
pub mod gamenight_id; pub mod gamenight_id;

View File

@ -0,0 +1,44 @@
use std::fmt::Display;
use async_trait::async_trait;
use gamenight_api_client_rs::{apis::default_api::game_post, models::{AddGameRequestBody, Game}};
use inquire::{Confirm, Text};
use super::*;
#[derive(Clone)]
pub struct AddGame {
}
impl AddGame {
pub fn new() -> Self {
Self {}
}
}
#[async_trait]
impl<'a> Flow<'a> for AddGame {
async fn run(&self, state: &'a mut GamenightState) -> FlowResult<'a> {
if let Some(name) = Text::new("What is the game you want to add").prompt_skippable()? {
let add_game_request = AddGameRequestBody {
name
};
game_post(&state.api_configuration, Some(add_game_request)).await?;
if let Some(owned) = Confirm::new("Do you own this game?").prompt_skippable()? {
if owned {
todo!()
}
}
}
Ok((FlowOutcome::Cancelled, state))
}
}
impl Display for AddGame {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Add Game")
}
}

View File

@ -0,0 +1,31 @@
use gamenight_api_client_rs::models::Game;
use super::*;
#[derive(Clone)]
pub struct EditGame {
game: Game
}
impl EditGame {
pub fn new(game: Game) -> Self {
Self {
game
}
}
}
#[async_trait]
impl<'a> Flow<'a> for EditGame {
async fn run(&self, state: &'a mut GamenightState) -> FlowResult<'a> {
todo!()
}
}
impl Display for EditGame {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "")
}
}

View File

@ -1,7 +1,7 @@
use inquire::{ui::RenderConfig, Select}; use inquire::{ui::RenderConfig, Select};
use crate::flows::{add_gamenight::AddGamenight, exit::Exit, list_gamenights::ListGamenights}; use crate::flows::{add_gamenight::AddGamenight, exit::Exit, games::Games, list_gamenights::ListGamenights};
use super::*; use super::*;
@ -26,6 +26,7 @@ impl<'a> Flow<'a> for GamenightMenu {
let flows: Vec<Box<dyn Flow + Send>> = vec![ let flows: Vec<Box<dyn Flow + Send>> = vec![
Box::new(ListGamenights::new()), Box::new(ListGamenights::new()),
Box::new(AddGamenight::new()), Box::new(AddGamenight::new()),
Box::new(Games::new()),
Box::new(Exit::new()) Box::new(Exit::new())
]; ];

View File

@ -0,0 +1,47 @@
use std::fmt::Display;
use async_trait::async_trait;
use inquire::{ui::RenderConfig, Select};
use crate::flows::{add_game::AddGame, exit::Exit, list_games::ListGames};
use super::*;
#[derive(Clone)]
pub struct Games {
}
impl Games {
pub fn new() -> Self {
Self {}
}
}
#[async_trait]
impl<'a> Flow<'a> for Games {
async fn run(&self, state: &'a mut GamenightState) -> FlowResult<'a> {
let flows: Vec<Box<dyn Flow + Send>> = vec![
Box::new(ListGames::new()),
Box::new(AddGame::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()?;
clear_screen::clear();
handle_choice_option(&choice, self, state).await
}
}
impl Display for Games {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Games")
}
}

View File

@ -0,0 +1,51 @@
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::{edit_game::EditGame, exit::Exit};
use super::*;
#[derive(Clone)]
pub struct ListGames {
}
impl ListGames {
pub fn new() -> Self {
Self {}
}
}
#[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 mut flows = games.into_iter().map(|game| -> Box<dyn Flow + Send> {
Box::new(EditGame::new(game))
}).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()?;
clear_screen::clear();
handle_choice_option(&choice, self, state).await
}
}
impl Display for ListGames {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "List all games")
}
}

View File

@ -20,6 +20,10 @@ mod join;
mod leave; mod leave;
mod connect; mod connect;
mod settings; mod settings;
mod games;
mod list_games;
mod add_game;
mod edit_game;
pub struct GamenightState { pub struct GamenightState {
api_configuration: Configuration, api_configuration: Configuration,

View File

@ -1,2 +1,2 @@
[print_schema] [print_schema]
file = "src/schema/schema.rs" file = "src/schema.rs"

View File

@ -1,4 +1,4 @@
-- This file should undo anything in `up.sql` -- This file should undo anything in `up.sql`
drop table gamenight; drop table gamenight;
drop table known_games; drop table game;

View File

@ -6,7 +6,7 @@ CREATE TABLE gamenight (
datetime VARCHAR NOT NULL datetime VARCHAR NOT NULL
); );
CREATE TABLE known_games ( CREATE TABLE game (
id UUID NOT NULL PRIMARY KEY, id UUID NOT NULL PRIMARY KEY,
name VARCHAR UNIQUE NOT NULL name VARCHAR UNIQUE NOT NULL
); );

View File

@ -1,6 +1,6 @@
-- This file should undo anything in `up.sql` -- This file should undo anything in `up.sql`
drop table pwd; drop table pwd;
drop table users; drop table client;
drop type Role; drop type Role;

View File

@ -1,6 +1,6 @@
CREATE TYPE Role AS ENUM ('user', 'admin'); CREATE TYPE Role AS ENUM ('user', 'admin');
CREATE TABLE users ( CREATE TABLE client (
id UUID NOT NULL PRIMARY KEY, id UUID NOT NULL PRIMARY KEY,
username VARCHAR UNIQUE NOT NULL, username VARCHAR UNIQUE NOT NULL,
email VARCHAR UNIQUE NOT NULL, email VARCHAR UNIQUE NOT NULL,
@ -10,7 +10,7 @@ CREATE TABLE users (
CREATE TABLE pwd ( CREATE TABLE pwd (
user_id UUID NOT NULL PRIMARY KEY, user_id UUID NOT NULL PRIMARY KEY,
password VARCHAR NOT NULL, password VARCHAR NOT NULL,
CONSTRAINT FK_UserId FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE CONSTRAINT FK_UserId FOREIGN KEY (user_id) REFERENCES client(id) ON DELETE CASCADE
); );
--Initialize default admin user, with password "gamenight!" --Initialize default admin user, with password "gamenight!"
@ -19,7 +19,7 @@ DO $$
DECLARE DECLARE
admin_uuid uuid = uuid_generate_v4(); admin_uuid uuid = uuid_generate_v4();
BEGIN BEGIN
INSERT INTO users (id, username, email, role) INSERT INTO client (id, username, email, role)
values(admin_uuid, 'admin', '', 'admin'); values(admin_uuid, 'admin', '', 'admin');
insert INTO pwd (user_id, password) insert INTO pwd (user_id, password)

View File

@ -5,7 +5,7 @@ CREATE TABLE gamenight (
name VARCHAR NOT NULL, name VARCHAR NOT NULL,
datetime VARCHAR NOT NULL, datetime VARCHAR NOT NULL,
owner_id UUID NOT NULL, owner_id UUID NOT NULL,
CONSTRAINT FK_OwnerId FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE CASCADE CONSTRAINT FK_OwnerId FOREIGN KEY (owner_id) REFERENCES client(id) ON DELETE CASCADE
); );
SET session_replication_role = 'replica'; SET session_replication_role = 'replica';

View File

@ -4,6 +4,6 @@ create table gamenight_gamelist (
gamenight_id UUID NOT NULL, gamenight_id UUID NOT NULL,
game_id UUID NOT NULL, game_id UUID NOT NULL,
CONSTRAINT FK_gamenight_id FOREIGN KEY (gamenight_id) REFERENCES gamenight(id) ON DELETE CASCADE, CONSTRAINT FK_gamenight_id FOREIGN KEY (gamenight_id) REFERENCES gamenight(id) ON DELETE CASCADE,
CONSTRAINT FK_game_id FOREIGN KEY (game_id) REFERENCES known_games(id) ON DELETE CASCADE, CONSTRAINT FK_game_id FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE,
PRIMARY KEY(gamenight_id, game_id) PRIMARY KEY(gamenight_id, game_id)
); );

View File

@ -4,6 +4,6 @@ create table gamenight_participant (
gamenight_id UUID NOT NULL, gamenight_id UUID NOT NULL,
user_id UUID NOT NULL, user_id UUID NOT NULL,
CONSTRAINT FK_gamenight_id FOREIGN KEY (gamenight_id) REFERENCES gamenight(id) ON DELETE CASCADE, CONSTRAINT FK_gamenight_id FOREIGN KEY (gamenight_id) REFERENCES gamenight(id) ON DELETE CASCADE,
CONSTRAINT FK_user_id FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, CONSTRAINT FK_user_id FOREIGN KEY (user_id) REFERENCES client(id) ON DELETE CASCADE,
PRIMARY KEY(gamenight_id, user_id) PRIMARY KEY(gamenight_id, user_id)
) )

View File

@ -0,0 +1,3 @@
-- This file should undo anything in `up.sql`
drop table owned_game;

View File

@ -0,0 +1,9 @@
-- Your SQL goes here
create table owned_game (
user_id UUID NOT NULL,
game_id UUID NOT NULL,
CONSTRAINT FK_gamenight_id FOREIGN KEY (user_id) REFERENCES client(id) ON DELETE CASCADE,
CONSTRAINT FK_user_id FOREIGN KEY (game_id) REFERENCES game(id) ON DELETE CASCADE,
PRIMARY KEY(user_id, game_id)
)

View File

@ -0,0 +1,21 @@
use diesel::{dsl::insert_into, Insertable, PgConnection, Queryable, RunQueryDsl};
use serde::{Serialize, Deserialize};
use uuid::Uuid;
use crate::schema::game;
use super::error::DatabaseError;
#[derive(Serialize, Deserialize, Debug, Insertable, Queryable)]
#[diesel(table_name = game)]
pub struct Game {
pub id: Uuid,
pub name: String,
}
pub fn games(conn: &mut PgConnection) -> Result<Vec::<Game>, DatabaseError> {
Ok(game::table.load::<Game>(conn)?)
}
pub fn insert_game(conn: &mut PgConnection, game: Game) -> Result<usize, DatabaseError> {
Ok(insert_into(game::table).values(&game).execute(conn)?)
}

View File

@ -3,6 +3,7 @@ pub mod error;
pub mod schema; pub mod schema;
pub mod gamenight; pub mod gamenight;
pub mod gamenight_participants; pub mod gamenight_participants;
pub mod game;
use diesel::r2d2::ConnectionManager; use diesel::r2d2::ConnectionManager;
use diesel::r2d2::ManageConnection; use diesel::r2d2::ManageConnection;
@ -17,6 +18,7 @@ pub use user::login;
pub use user::register; pub use user::register;
pub use gamenight::gamenights; pub use gamenight::gamenights;
pub use gamenight_participants::get_participants; pub use gamenight_participants::get_participants;
pub use game::games;
pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!(); pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!();
pub type DbConnection = PgConnection; pub type DbConnection = PgConnection;

View File

@ -6,6 +6,25 @@ pub mod sql_types {
pub struct Role; pub struct Role;
} }
diesel::table! {
use diesel::sql_types::*;
use super::sql_types::Role;
client (id) {
id -> Uuid,
username -> Varchar,
email -> Varchar,
role -> Role,
}
}
diesel::table! {
game (id) {
id -> Uuid,
name -> Varchar,
}
}
diesel::table! { diesel::table! {
gamenight (id) { gamenight (id) {
id -> Uuid, id -> Uuid,
@ -30,9 +49,9 @@ diesel::table! {
} }
diesel::table! { diesel::table! {
known_games (id) { owned_game (user_id, game_id) {
id -> Uuid, user_id -> Uuid,
name -> Varchar, game_id -> Uuid,
} }
} }
@ -53,31 +72,22 @@ diesel::table! {
} }
} }
diesel::table! { diesel::joinable!(gamenight -> client (owner_id));
use diesel::sql_types::*; diesel::joinable!(gamenight_gamelist -> game (game_id));
use super::sql_types::Role;
users (id) {
id -> Uuid,
username -> Varchar,
email -> Varchar,
role -> Role,
}
}
diesel::joinable!(gamenight -> users (owner_id));
diesel::joinable!(gamenight_gamelist -> gamenight (gamenight_id)); diesel::joinable!(gamenight_gamelist -> gamenight (gamenight_id));
diesel::joinable!(gamenight_gamelist -> known_games (game_id)); diesel::joinable!(gamenight_participant -> client (user_id));
diesel::joinable!(gamenight_participant -> gamenight (gamenight_id)); diesel::joinable!(gamenight_participant -> gamenight (gamenight_id));
diesel::joinable!(gamenight_participant -> users (user_id)); diesel::joinable!(owned_game -> client (user_id));
diesel::joinable!(pwd -> users (user_id)); diesel::joinable!(owned_game -> game (game_id));
diesel::joinable!(pwd -> client (user_id));
diesel::allow_tables_to_appear_in_same_query!( diesel::allow_tables_to_appear_in_same_query!(
client,
game,
gamenight, gamenight,
gamenight_gamelist, gamenight_gamelist,
gamenight_participant, gamenight_participant,
known_games, owned_game,
pwd, pwd,
registration_tokens, registration_tokens,
users,
); );

View File

@ -1,83 +0,0 @@
// @generated automatically by Diesel CLI.
pub mod sql_types {
#[derive(diesel::sql_types::SqlType)]
#[diesel(postgres_type(name = "role"))]
pub struct Role;
}
diesel::table! {
gamenight (id) {
id -> Uuid,
name -> Varchar,
datetime -> Timestamptz,
owner_id -> Uuid,
}
}
diesel::table! {
gamenight_gamelist (gamenight_id, game_id) {
gamenight_id -> Uuid,
game_id -> Uuid,
}
}
diesel::table! {
gamenight_participant (gamenight_id, user_id) {
gamenight_id -> Uuid,
user_id -> Uuid,
}
}
diesel::table! {
known_games (id) {
id -> Uuid,
name -> Varchar,
}
}
diesel::table! {
pwd (user_id) {
user_id -> Uuid,
password -> Varchar,
}
}
diesel::table! {
registration_tokens (id) {
id -> Uuid,
#[max_length = 32]
token -> Bpchar,
single_use -> Bool,
expires -> Nullable<Timestamptz>,
}
}
diesel::table! {
use diesel::sql_types::*;
use super::sql_types::Role;
users (id) {
id -> Uuid,
username -> Varchar,
email -> Varchar,
role -> Role,
}
}
diesel::joinable!(gamenight -> users (owner_id));
diesel::joinable!(gamenight_gamelist -> gamenight (gamenight_id));
diesel::joinable!(gamenight_gamelist -> known_games (game_id));
diesel::joinable!(gamenight_participant -> gamenight (gamenight_id));
diesel::joinable!(gamenight_participant -> users (user_id));
diesel::joinable!(pwd -> users (user_id));
diesel::allow_tables_to_appear_in_same_query!(
gamenight,
gamenight_gamelist,
gamenight_participant,
known_games,
pwd,
registration_tokens,
users,
);

View File

@ -13,7 +13,7 @@ use argon2::password_hash::rand_core::OsRng;
use argon2::password_hash::rand_core::RngCore; use argon2::password_hash::rand_core::RngCore;
use crate::DbConnection; use crate::DbConnection;
use super::schema::{pwd, users}; use super::schema::{pwd, client};
pub use super::error::DatabaseError; pub use super::error::DatabaseError;
@ -26,7 +26,7 @@ struct Pwd {
} }
#[derive(Serialize, Deserialize, Debug, Insertable, Queryable)] #[derive(Serialize, Deserialize, Debug, Insertable, Queryable)]
#[diesel(table_name = users)] #[diesel(table_name = client)]
pub struct User { pub struct User {
pub id: Uuid, pub id: Uuid,
pub username: String, pub username: String,
@ -65,10 +65,10 @@ pub struct Register {
} }
pub fn login(conn: &mut DbConnection, user: LoginUser) -> Result<Option<User>, DatabaseError> { pub fn login(conn: &mut DbConnection, user: LoginUser) -> Result<Option<User>, DatabaseError> {
let id: Uuid = users::table let id: Uuid = client::table
.filter(users::username.eq(&user.username)) .filter(client::username.eq(&user.username))
.or_filter(users::email.eq(&user.username)) .or_filter(client::email.eq(&user.username))
.select(users::id) .select(client::id)
.first(conn)?; .first(conn)?;
let pwd: String = pwd::table let pwd: String = pwd::table
@ -82,14 +82,14 @@ pub fn login(conn: &mut DbConnection, user: LoginUser) -> Result<Option<User>, D
.verify_password(user.password.as_bytes(), &parsed_hash) .verify_password(user.password.as_bytes(), &parsed_hash)
.is_ok() .is_ok()
{ {
Ok(Some(users::table.find(id).first(conn)?)) Ok(Some(client::table.find(id).first(conn)?))
} else { } else {
Ok(None) Ok(None)
} }
} }
pub fn get_user(conn: &mut DbConnection, id: Uuid) -> Result<User, DatabaseError> { pub fn get_user(conn: &mut DbConnection, id: Uuid) -> Result<User, DatabaseError> {
Ok(users::table.find(id).first(conn)?) Ok(client::table.find(id).first(conn)?)
} }
pub fn register(conn: &mut DbConnection, register: Register) -> Result<(), DatabaseError> { pub fn register(conn: &mut DbConnection, register: Register) -> Result<(), DatabaseError> {
@ -105,7 +105,7 @@ pub fn register(conn: &mut DbConnection, register: Register) -> Result<(), Datab
conn.transaction(|c| { conn.transaction(|c| {
let id = Uuid::new_v4(); let id = Uuid::new_v4();
diesel::insert_into(users::table) diesel::insert_into(client::table)
.values(User { .values(User {
id, id,
username: register.username, username: register.username,
@ -126,15 +126,15 @@ pub fn register(conn: &mut DbConnection, register: Register) -> Result<(), Datab
} }
pub fn count_users_with_username(conn: &mut DbConnection, username: &String) -> Result<i64, DatabaseError> { pub fn count_users_with_username(conn: &mut DbConnection, username: &String) -> Result<i64, DatabaseError> {
Ok(users::table Ok(client::table
.count() .count()
.filter(users::username.eq(username)) .filter(client::username.eq(username))
.get_result::<i64>(conn)?) .get_result::<i64>(conn)?)
} }
pub fn count_users_with_email(conn: &mut DbConnection, email: &String) -> Result<i64, DatabaseError> { pub fn count_users_with_email(conn: &mut DbConnection, email: &String) -> Result<i64, DatabaseError> {
Ok(users::table Ok(client::table
.count() .count()
.filter(users::email.eq(email)) .filter(client::email.eq(email))
.get_result::<i64>(conn)?) .get_result::<i64>(conn)?)
} }