Compare commits

..

61 Commits
main ... main

Author SHA1 Message Date
28f7306d57 Adds Adding of Games to the system. 2025-06-27 22:08:23 +02:00
3f51d52edf Refactored the connect flow and make canceling behavior more consistent. 2025-06-27 16:31:48 +02:00
f0883a0ff0 Fixes leave on the server and reworked login flow. 2025-06-27 14:45:36 +02:00
fbe456a0f5 Implements leaving on the client side 2025-06-24 16:49:21 +02:00
d11e31149b Implemented leaving a gamenight on the server/database side 2025-06-15 19:12:46 +02:00
db6f55bc47 Rewrite to chatty but functional API. 2025-06-14 22:16:13 +02:00
d1832bc794 Last cleanup 2025-06-03 19:50:43 +02:00
f1d23cb495 Returns participants for a gamenight 2025-05-31 22:37:51 +02:00
156be1821a Cleaned up api client sources 2025-05-31 12:03:58 +02:00
6950ac62e8 Autogenerate only the models of the API for the backend-server 2025-05-31 11:38:55 +02:00
597a960bf1 Splits database into a separate crate 2025-05-30 14:31:00 +02:00
Dennis Brentjes
3f7ed03973 Fixes some Clippy remarks. 2025-05-19 21:01:38 +02:00
c994321576 Started on separating domain and adapters 2025-05-14 07:41:28 +02:00
4e26d3cdcb Created a domain module to decouple flows from the core. 2025-05-03 22:49:01 +02:00
fce0ebd76b Deleted the old Rust backend based on rocket. 2025-05-02 23:06:38 +02:00
6699dcf392 Added some more gamenight-cli Flows. 2025-05-02 23:03:57 +02:00
db25dc0aed Started working on a cli frontend. 2025-04-23 20:27:06 +02:00
02913c7b52 upgrades packages. 2025-03-30 22:54:21 +02:00
Dennis Brentjes
9e84a62c41 Added Avanlonia frontend. 2023-08-15 11:31:58 +02:00
22f05c00c1 Added a client (From generator) and a basic login page to test it. 2023-04-30 20:51:42 +02:00
b2aba31264 Added initial Uno Platform frontend project. 2023-04-30 17:23:00 +02:00
Dennis Brentjes
1296f363af Re-added auto migrations 2023-03-30 17:08:44 +02:00
Dennis Brentjes
70ae15f655 Fixes the ugly Register User post handler. 2023-03-30 09:32:24 +02:00
3509a70a6a Abstracted away getting a PgConnection with expect(). 2023-03-26 11:25:15 +02:00
217e5ee64b Adds get for a single gamenight. 2023-03-25 23:32:41 +01:00
5216f55a14 Adds the post gamenight handler. 2023-03-25 22:44:58 +01:00
534e6867d8 Adds user authorization to the actix backend. 2023-03-25 19:20:38 +01:00
1c8110cdb0 Added Login and Register handlers for actix backend 2023-03-24 22:28:18 +01:00
d961896242 Started reimplementation of the Rest api in actix-web 2023-03-17 22:20:26 +01:00
7741c1dbae Merge pull request 'Adds an AdminPanel with currently active registration tokens.' (#9) from admin-panel into main
Reviewed-on: Roflin/gamenight#9
2022-06-05 16:15:50 +02:00
65d2dece55 Adds an AdminPanel with currently active registration tokens. 2022-06-04 21:57:54 +02:00
34737bfb6b Updates all libraries and some cleanup in the Rust part. 2022-06-04 13:10:09 +02:00
5ace39d820 Merge pull request 'join_gamenight' (#8) from join_gamenight into main
Reviewed-on: Roflin/gamenight#8
2022-06-03 19:47:02 +02:00
b7f981e3a6 Adds the ability to join or leave a gamenight. 2022-05-31 21:27:35 +02:00
f7f9f7456b Adds a Apihelper to cleanup the react components. 2022-05-31 19:56:44 +02:00
cfaa6ebdb1 Merge pull request 'Adds some basic styling' (#7) from some-styling-work into main
Reviewed-on: Roflin/gamenight#7
2022-05-30 21:35:58 +02:00
8a318e877f Merge pull request 'gamenight-participants' (#6) from gamenight-participants into main
Reviewed-on: Roflin/gamenight#6
2022-05-30 21:32:47 +02:00
bcfcf66df5 Merge pull request 'Adds the ability to add games with suggestions from known games.' (#3) from game-adding-to-gamenight into main
Reviewed-on: Roflin/gamenight#3
2022-05-30 21:31:01 +02:00
83a0b5ad9d Adds some basic styling 2022-05-29 18:26:08 +02:00
9de8ffaa2d Some Cleaup. 2022-05-29 10:46:05 +02:00
102a3e6082 Formatting commit 2022-05-29 10:33:55 +02:00
639405bf9f Adds a details page for a single gamenight. 2022-05-29 10:33:19 +02:00
2ba2026e21 Gamenights also return their game list. 2022-05-29 10:33:19 +02:00
86cdbedd41 Adds the participants part of the API. 2022-05-29 10:33:19 +02:00
836a4ab59f Formatting commit. 2022-05-29 10:33:19 +02:00
5c27be0191 Schema rewrite to split up the schema.rs file. 2022-05-29 10:33:19 +02:00
1a6ead4760 Adds the ability to add games with suggestions from known games. 2022-05-29 10:32:20 +02:00
5ffeea6553 Merge pull request 'initial-frontend-work' (#2) from initial-frontend-work into main
Reviewed-on: Roflin/gamenight#2
2022-05-27 20:30:06 +02:00
cc26aed9a5 Reworked the database code to make use of the ? operator. 2022-05-14 23:44:40 +02:00
92e0257e74 Added gamenight owners and some ui and api functions to delete gamenights. 2022-05-14 23:36:35 +02:00
0a214ca388 Fixes the infinite loop and refactores some statechanges into useEffect hooks 2022-05-01 17:51:28 +02:00
2cfaf2b4cc Adds an add gamenight control and fixes the fetch gamenight Effect,
Introduces an infinite fetch gamenights loop
2022-04-29 22:40:10 +02:00
bf796201bf move to function based react components 2022-04-29 20:27:54 +02:00
56d0889963 A start on a frontend application in React. 2022-04-23 23:30:26 +02:00
d80f705b5d Merge pull request 'Added a user system with no proper user validation but working authorisation.' (#1) from user-system into main
Reviewed-on: Roflin/gamenight#1
2022-04-23 13:17:28 +02:00
aab60dcc11 Fixes 3 potential panics when querying the user and pwd table. 2022-04-21 21:51:57 +02:00
b5e9420c1f Makes seperate function for authorized and unauthorized request. 2022-04-21 21:35:14 +02:00
5f73d556c6 Fixes the review comments 2022-04-21 20:02:15 +02:00
81e65b1619 Ran rust fmt. 2022-04-21 19:12:16 +02:00
df8b553345 Adds some validation to new user registration. 2022-04-21 18:51:13 +02:00
af0dcee159 Added a user system with no proper user validation but working authorisation. 2022-04-20 22:28:00 +02:00
125 changed files with 8958 additions and 2315 deletions

6
.gitignore vendored
View File

@ -1,4 +1,4 @@
/target
/target/
**/*.rs.bk
Cargo.lock
.vscode
Rocket.toml
*.sqlite

2019
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,23 +0,0 @@
[package]
name = "gamenight"
version = "0.1.0"
authors = ["Dennis Brentjes <d.brentjes@gmail.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
rocket = { version = "0.5.0-rc.1", features = ["default", "json"] }
libsqlite3-sys = { version = ">=0.8.0, <0.19.0", features = ["bundled"] }
rocket_sync_db_pools = { version = "0.1.0-rc.1", features = ["diesel_sqlite_pool"] }
diesel = { version = "1.4.8", features = ["sqlite"] }
diesel_migrations = "1.4.0"
rocket_dyn_templates = { version = "0.1.0-rc.1", features = ["handlebars"] }
chrono = "0.4.19"
serde = "1.0.136"
[profile.dev]
overflow-checks = true
[profile.release]
overflow-checks = true

View File

@ -1,4 +0,0 @@
#Copy this file over to Rocket.toml after changing all relevant values.
[global.databases]
gamenight_database = { url = "gamenight.sqlite" }

3
backend-actix/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
target
src/models/
docs/

2417
backend-actix/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

20
backend-actix/Cargo.toml Normal file
View File

@ -0,0 +1,20 @@
[package]
name = "backend-actix"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
gamenight-database = { path = "../gamenight-database"}
actix-web = "4"
actix-cors = "0.7"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
uuid = { version = "1.3.0", features = ["serde", "v4"] }
chrono = { version = "0.4", features = ["serde"] }
jsonwebtoken = "9.3"
validator = { version = "0.20", features = ["derive"] }
rand_core = { version = "0.9" }
env_logger = "0.11"
tracing-actix-web = "0.7"

30
backend-actix/build.rs Normal file
View File

@ -0,0 +1,30 @@
use std::{fs::{exists, read_dir, remove_dir_all, File}, io::Write, process::Command};
fn main() {
println!("cargo::rerun-if-changed=gamenight-api.yaml");
if exists("src/models").unwrap() {
remove_dir_all("src/models").unwrap();
}
let _ =
Command::new("openapi-generator")
.args(["generate", "-i", "gamenight-api.yaml", "-g", "rust", "--global-property", "models"])
.output()
.expect("Failed to generate models sources for the gamenight API");
let mut file = File::create("src/models/mod.rs").unwrap();
let paths = read_dir("./src/models").unwrap();
for path in paths {
let path = path.unwrap();
let path = path.path();
let stem = path.file_stem().unwrap();
if stem == "mod" {
continue
}
let line = format!("pub mod {};\n", stem.to_str().unwrap());
let _ = file.write(line.as_bytes()).unwrap();
}
}

View File

@ -0,0 +1,447 @@
openapi: 3.0.0
info:
title: Gamenight
version: '1.0'
contact:
name: Dennis Brentjes
email: dennis@brentj.es
url: 'https://brentj.es'
description: Api specifaction for a Gamenight server
license:
name: MIT
servers:
- url: 'http://localhost:8080'
description: Gamenight
paths:
/token:
get:
summary: ''
operationId: get-token
responses:
'200':
$ref: '#/components/responses/TokenResponse'
'401':
$ref: '#/components/responses/FailureResponse'
requestBody:
$ref: '#/components/requestBodies/LoginRequest'
description: Submit your credentials to get a JWT-token to use with the rest of the api.
parameters: []
post:
summary: ''
operationId: post-token
responses:
'200':
$ref: '#/components/responses/TokenResponse'
'401':
$ref: '#/components/responses/FailureResponse'
description: Refresh your JWT-token without logging in again.
parameters: []
security:
- JWT-Auth: []
/user:
post:
summary: ''
operationId: post-register
requestBody:
$ref: '#/components/requestBodies/RegisterRequest'
responses:
'200':
description: ''
'422':
$ref: '#/components/responses/FailureResponse'
description: 'Create a new user given a registration token and user information, username and email must be unique, and password and password_repeat must match.'
parameters: []
security:
- JWT-Auth: []
get:
description: 'Get a user from primary id'
requestBody:
$ref: '#/components/requestBodies/GetUserRequest'
parameters: []
responses:
'200':
$ref: '#/components/responses/UserResponse'
'401':
$ref: '#/components/responses/FailureResponse'
'404':
$ref: '#/components/responses/FailureResponse'
'422':
$ref: '#/components/responses/FailureResponse'
security:
- JWT-Auth: []
/gamenights:
get:
summary: Get a all gamenights
responses:
'200':
$ref: '#/components/responses/GamenightsResponse'
'400':
$ref: '#/components/responses/FailureResponse'
'401':
$ref: '#/components/responses/FailureResponse'
operationId: get-gamenights
security:
- JWT-Auth: []
description: Retrieve the list of gamenights on this gamenight server. Requires authorization.
/participants:
get:
summary: Get all participants for a gamenight
responses:
'200':
$ref: '#/components/responses/ParticipantsResponse'
'400':
$ref: '#/components/responses/FailureResponse'
'401':
$ref: '#/components/responses/FailureResponse'
requestBody:
$ref: '#/components/requestBodies/GetParticipants'
security:
- JWT-Auth: []
description: Retrieve the participants of a single gamenight by id.
/gamenight:
post:
summary: ''
operationId: post-gamenight
responses:
'200':
description: OK
'401':
$ref: '#/components/responses/FailureResponse'
'422':
$ref: '#/components/responses/FailureResponse'
security:
- JWT-Auth: []
requestBody:
$ref: '#/components/requestBodies/AddGamenight'
description: 'Add a gamenight by providing a name and a date, only available when providing an JWT token.'
get:
summary: ''
operationId: get-gamenight
responses:
'200':
$ref: '#/components/responses/GamenightResponse'
'401':
$ref: '#/components/responses/FailureResponse'
'422':
$ref: '#/components/responses/FailureResponse'
requestBody:
$ref: '#/components/requestBodies/GetGamenight'
security:
- JWT-Auth: []
/join:
post:
responses:
'200':
description: OK
'401':
$ref: '#/components/responses/FailureResponse'
'422':
$ref: '#/components/responses/FailureResponse'
requestBody:
$ref: '#/components/requestBodies/JoinGamenight'
security:
- JWT-Auth: []
/leave:
post:
responses:
'200':
description: "OK"
'401':
$ref: '#/components/responses/FailureResponse'
'422':
$ref: '#/components/responses/FailureResponse'
requestBody:
$ref: '#/components/requestBodies/LeaveGamenight'
security:
- 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:
schemas:
Gamenight:
title: Gamenight
type: object
properties:
id:
type: string
name:
type: string
datetime:
type: string
owner_id:
type: string
required:
- id
- name
- datetime
- owner_id
Participants:
title: participants
type: object
properties:
participants:
type: array
items:
type: string
required:
- participants
Failure:
title: Failure
type: object
properties:
message:
type: string
description: 'Failure Reason'
Token:
title: Token
type: object
properties:
jwt_token:
type: string
Login:
title: Login
type: object
properties:
username:
type: string
password:
type: string
required:
- username
- password
Registration:
title: Registration
type: object
properties:
username:
type: string
email:
type: string
password:
type: string
password_repeat:
type: string
registration_token:
type: string
required:
- username
- email
- password
- password_repeat
- registration_token
UserId:
title: UserId
type: object
properties:
user_id:
type: string
required:
- user_id
AddGamenightRequestBody:
title: AddGamenightRequestBody
type: object
properties:
name:
type: string
datetime:
type: string
required:
- name
- datetime
GamenightId:
title: GamenightId
type: object
properties:
gamenight_id:
type: string
required:
- gamenight_id
GameId:
title: GameId
type: object
properties:
game_id:
type: string
required:
- game_id
GetGamenightRequestBody:
type: object
properties:
id:
type: string
User:
type: object
properties:
id:
type: string
username:
type: string
email:
type: string
required:
- id
- username
Game:
type: object
properties:
id:
type: string
name:
type: string
required:
- id
- name
AddGameRequestBody:
type: object
properties:
name:
type: string
required:
- name
requestBodies:
LoginRequest:
content:
application/json:
schema:
$ref: '#/components/schemas/Login'
RegisterRequest:
content:
application/json:
schema:
$ref: '#/components/schemas/Registration'
GetUserRequest:
content:
application/json:
schema:
$ref: '#/components/schemas/UserId'
AddGamenight:
content:
application/json:
schema:
$ref: '#/components/schemas/AddGamenightRequestBody'
GetGamenight:
content:
application/json:
schema:
$ref: '#/components/schemas/GetGamenightRequestBody'
GetParticipants:
content:
application/json:
schema:
$ref: '#/components/schemas/GamenightId'
JoinGamenight:
content:
application/json:
schema:
$ref: '#/components/schemas/GamenightId'
LeaveGamenight:
content:
application/json:
schema:
$ref: '#/components/schemas/GamenightId'
GetGameRequest:
content:
application/json:
schema:
$ref: '#/components/schemas/GameId'
AddGameRequest:
content:
application/json:
schema:
$ref: '#/components/schemas/AddGameRequestBody'
responses:
TokenResponse:
description: Example response
content:
application/json:
schema:
$ref: '#/components/schemas/Token'
FailureResponse:
description: Example response
content:
application/json:
schema:
$ref: '#/components/schemas/Failure'
ParticipantsResponse:
description: Response with a list of participant uuids
content:
application/json:
schema:
$ref: '#/components/schemas/Participants'
GamenightsResponse:
description: Example response
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Gamenight'
GamenightResponse:
description: A gamenight being hosted
content:
application/json:
schema:
$ref: '#/components/schemas/Gamenight'
UserResponse:
description: A user in the gamenight system
content:
application/json:
schema:
$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:
JWT-Auth:
type: http
scheme: bearer
bearerFormat: JWT
description: ''

57
backend-actix/src/main.rs Normal file
View File

@ -0,0 +1,57 @@
#[allow(unused_imports)]
pub mod models;
pub mod request;
use actix_cors::Cors;
use actix_web::middleware::Logger;
use actix_web::HttpServer;
use actix_web::App;
use actix_web::http;
use actix_web::web;
use request::{*, login, register, gamenights};
use tracing_actix_web::TracingLogger;
use gamenight_database::*;
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let url = "postgres://root:root@127.0.0.1/gamenight";
let pool = get_connection_pool(url);
let mut conn = pool.get_conn();
run_migration(&mut conn);
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
HttpServer::new(move || {
let cors = Cors::default()
.allowed_origin("0.0.0.0")
.allowed_origin_fn(|_origin, _req_head| { true })
.allowed_methods(vec!["GET", "POST"])
.allowed_headers(vec![http::header::AUTHORIZATION, http::header::ACCEPT])
.allowed_header(http::header::CONTENT_TYPE)
.max_age(3600);
App::new()
.wrap(cors)
.wrap(Logger::default())
.wrap(TracingLogger::default())
.app_data(web::Data::new(pool.clone()))
.service(login)
.service(refresh)
.service(register)
.service(gamenights)
.service(gamenight_post)
.service(gamenight_get)
.service(get_user)
.service(get_user_unauthenticated)
.service(post_join_gamenight)
.service(post_leave_gamenight)
.service(get_get_participants)
.service(get_games)
.service(post_game)
})
.bind(("::1", 8080))?
.run()
.await
}

View File

@ -0,0 +1,77 @@
use std::future::{Ready, ready};
use actix_web::{FromRequest, http, HttpRequest, dev::Payload, web::Data};
use chrono::Utc;
use jsonwebtoken::{encode, Header, EncodingKey, decode, DecodingKey, Validation};
use serde::{Serialize, Deserialize};
use uuid::Uuid;
use gamenight_database::{user::{get_user, User}, DbPool};
use super::error::ApiError;
#[derive(Debug, Serialize, Deserialize)]
pub struct Claims {
exp: i64,
uid: Uuid
}
pub struct AuthUser(pub User);
// pub struct AuthUser {
// pub id: Uuid,
// pub username: String,
// pub email: String,
// pub role: Role,
// }
impl From<User> for AuthUser {
fn from(value: User) -> Self {
Self(value)
}
}
fn get_claims(req: &HttpRequest) -> Result<Claims, ApiError> {
let token = req.headers()
.get(http::header::AUTHORIZATION)
.map(|h| h.to_str().unwrap().split_at(7).1.to_string());
let token = token.ok_or(ApiError{
status: 400,
message: "JWT-token was not specified in the Authorization header as Bearer: token".to_string()
})?;
let secret = "secret";
Ok(decode::<Claims>(token.as_str(), &DecodingKey::from_secret(secret.as_bytes()), &Validation::default())?.claims)
}
pub fn get_token(user: &User) -> Result<String, ApiError> {
let claims = Claims {
exp: Utc::now().timestamp() + chrono::Duration::days(7).num_seconds(),
uid: user.id,
};
let secret = "secret";
Ok(encode(
&Header::default(),
&claims,
&EncodingKey::from_secret(secret.as_bytes()))?)
}
impl FromRequest for AuthUser {
type Error = ApiError;
type Future = Ready<Result<Self, Self::Error>>;
fn from_request(req: &HttpRequest, _payload: &mut Payload) -> Self::Future {
ready(
(|| -> Result<AuthUser, ApiError>{
let pool = req.app_data::<Data<DbPool>>().expect("No database configured");
let mut conn = pool.get().expect("couldn't get db connection from pool");
let uid = get_claims(req)?.uid;
let user = get_user(&mut conn, uid)?;
Ok(user.into())
})()
)
}
}

View File

@ -0,0 +1,91 @@
use std::fmt::{Display, Formatter, Result};
use actix_web::{ResponseError, error::BlockingError, HttpResponse, http::{header::ContentType, StatusCode}};
use serde::{Serialize, Deserialize};
use validator::ValidationErrors;
use gamenight_database::error::DatabaseError;
#[derive(Serialize, Deserialize, Debug)]
pub struct ApiError {
#[serde(skip_serializing)]
pub status: u16,
pub message: String
}
impl Display for ApiError {
fn fmt(&self, f: &mut Formatter) -> Result {
write!(f, "{}", self.message)
}
}
impl ResponseError for ApiError {
fn error_response(&self) -> HttpResponse {
HttpResponse::build(StatusCode::from_u16(self.status).unwrap())
.content_type(ContentType::json())
.body(serde_json::to_string(&self).unwrap())
}
}
impl From<DatabaseError> for ApiError {
fn from(value: DatabaseError) -> Self {
ApiError {
//Todo, split this in unrecoverable and schema error
status: 500,
message: value.0
}
}
}
impl From<BlockingError> for ApiError {
fn from(value: BlockingError) -> Self {
ApiError {
status: 500,
message: value.to_string()
}
}
}
impl From<serde_json::Error> for ApiError {
fn from(value: serde_json::Error) -> Self {
ApiError {
status: 500,
message: value.to_string()
}
}
}
impl From<jsonwebtoken::errors::Error> for ApiError {
fn from(value: jsonwebtoken::errors::Error) -> Self {
ApiError {
status: 500,
message: value.to_string()
}
}
}
impl From<ValidationErrors> for ApiError {
fn from(value: ValidationErrors) -> Self {
ApiError {
status: 422,
message: value.to_string()
}
}
}
impl From<chrono::ParseError> for ApiError {
fn from(value: chrono::ParseError) -> Self {
ApiError {
status: 422,
message: value.to_string()
}
}
}
impl From<uuid::Error> for ApiError {
fn from(value: uuid::Error) -> Self {
ApiError {
status: 422,
message: value.to_string()
}
}
}

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

@ -0,0 +1,70 @@
use actix_web::{get, web, Responder, http::header::ContentType, HttpResponse, post};
use chrono::{DateTime, ParseError};
use uuid::Uuid;
use gamenight_database::{gamenight, DbPool, GetConnection};
use crate::{models::{add_gamenight_request_body::AddGamenightRequestBody, gamenight::Gamenight, get_gamenight_request_body::GetGamenightRequestBody}, request::authorization::AuthUser};
use crate::request::error::ApiError;
impl AddGamenightRequestBody {
pub fn into_with_user(&self, user: AuthUser) -> Result<gamenight::Gamenight, ParseError> {
Ok(gamenight::Gamenight {
datetime: DateTime::parse_from_rfc3339(&self.datetime)?.with_timezone(&chrono::Utc),
id: Uuid::new_v4(),
name: self.name.clone(),
owner_id: user.0.id
})
}
}
impl From<GetGamenightRequestBody> for Uuid {
fn from(value: GetGamenightRequestBody) -> Self {
Uuid::parse_str(value.id.unwrap().as_str()).unwrap()
}
}
#[get("/gamenights")]
pub async fn gamenights(pool: web::Data<DbPool>, _user: AuthUser) -> Result<impl Responder, ApiError> {
let mut conn = pool.get_conn();
let gamenights: Vec<gamenight::Gamenight> = gamenight_database::gamenights(&mut conn)?;
let model: Vec<Gamenight> = gamenights.iter().map(|x| {
Gamenight {
id: x.id.to_string(),
name: x.name.clone(),
datetime: x.datetime.to_rfc3339(),
owner_id: x.owner_id.to_string()
}}
).collect();
Ok(HttpResponse::Ok()
.content_type(ContentType::json())
.body(serde_json::to_string(&model)?)
)
}
#[post("/gamenight")]
pub async fn gamenight_post(pool: web::Data<DbPool>, user: AuthUser, gamenight_data: web::Json<AddGamenightRequestBody>) -> Result<impl Responder, ApiError> {
let mut conn = pool.get_conn();
gamenight::add_gamenight(&mut conn, gamenight_data.into_with_user(user)?)?;
Ok(HttpResponse::Ok())
}
#[get("/gamenight")]
pub async fn gamenight_get(pool: web::Data<DbPool>, _user: AuthUser, gamenight_data: web::Json<GetGamenightRequestBody>) -> Result<impl Responder, ApiError> {
let mut conn = pool.get_conn();
let gamenight = gamenight::get_gamenight(&mut conn, gamenight_data.into_inner().into())?;
let model = Gamenight{
id: gamenight.id.to_string(),
datetime: gamenight.datetime.to_rfc3339(),
name: gamenight.name,
owner_id: gamenight.owner_id.to_string(),
};
Ok(HttpResponse::Ok()
.content_type(ContentType::json())
.body(serde_json::to_string(&model)?))
}

View File

@ -0,0 +1,37 @@
use actix_web::{post, web, HttpResponse, Responder};
use gamenight_database::{gamenight_participants::{delete_gamenight_participant, insert_gamenight_participant, GamenightParticipant}, DbPool, GetConnection};
use uuid::Uuid;
use crate::{models::gamenight_id::GamenightId, request::{authorization::AuthUser, error::ApiError}};
#[post("/join")]
pub async fn post_join_gamenight(pool: web::Data<DbPool>, user: AuthUser, gamenight_id: web::Json<GamenightId>) -> Result<impl Responder, ApiError> {
web::block(move || -> Result<usize, ApiError> {
let mut conn = pool.get_conn();
Ok(insert_gamenight_participant(&mut conn, GamenightParticipant {
gamenight_id: Uuid::parse_str(&gamenight_id.gamenight_id)?,
user_id: user.0.id
})?)
}).await??;
Ok(HttpResponse::Ok())
}
#[post("/leave")]
pub async fn post_leave_gamenight(pool: web::Data<DbPool>, user: AuthUser, gamenight_id: web::Json<GamenightId>) -> Result<impl Responder, ApiError> {
web::block(move || -> Result<usize, ApiError> {
let mut conn = pool.get_conn();
let participant = GamenightParticipant {
gamenight_id: Uuid::parse_str(&gamenight_id.gamenight_id)?,
user_id: user.0.id
};
println!("{:?}", participant);
let x = delete_gamenight_participant(&mut conn, participant)?;
println!("Amount of deleted rows: {:?}", x);
Ok(x)
}).await??;
Ok(HttpResponse::Ok())
}

View File

@ -0,0 +1,22 @@
mod user_handlers;
mod gamenight_handlers;
mod error;
mod authorization;
mod join_gamenight;
mod participant_handlers;
mod game;
pub use user_handlers::login;
pub use user_handlers::refresh;
pub use user_handlers::register;
pub use gamenight_handlers::gamenights;
pub use gamenight_handlers::gamenight_post;
pub use gamenight_handlers::gamenight_get;
pub use user_handlers::get_user;
pub use user_handlers::get_user_unauthenticated;
pub use join_gamenight::post_join_gamenight;
pub use join_gamenight::post_leave_gamenight;
pub use participant_handlers::get_get_participants;
pub use game::get_games;
pub use game::post_game;

View File

@ -0,0 +1,17 @@
use actix_web::{get, http::header::ContentType, web, HttpResponse, Responder};
use gamenight_database::{DbPool, GetConnection};
use uuid::Uuid;
use crate::{models::{gamenight_id::GamenightId, participants::Participants}, request::{authorization::AuthUser, error::ApiError}};
#[get("/participants")]
pub async fn get_get_participants(pool: web::Data<DbPool>, _user: AuthUser, gamenight_info: web::Json<GamenightId>) -> Result<impl Responder, ApiError> {
let mut conn = pool.get_conn();
let users = gamenight_database::get_participants(&mut conn, &Uuid::parse_str(&gamenight_info.into_inner().gamenight_id)?)?
.iter().map(|x| x.to_string()).collect();
Ok(HttpResponse::Ok()
.content_type(ContentType::json())
.body(serde_json::to_string(&Participants{participants: users})?))
}

View File

@ -0,0 +1,165 @@
use actix_web::http::header::ContentType;
use actix_web::{get, post, web, HttpResponse, Responder};
use gamenight_database::user::{count_users_with_email, count_users_with_username};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use validator::{Validate, ValidateArgs, ValidationError};
use crate::models::login::Login;
use crate::models::registration::Registration;
use crate::models::token::Token;
use crate::models::user::User;
use crate::models::user_id::UserId;
use crate::request::error::ApiError;
use crate::request::authorization::get_token;
use serde_json;
use gamenight_database::{DbPool, GetConnection};
use super::authorization::AuthUser;
impl From<Login> for gamenight_database::user::LoginUser {
fn from(val: Login) -> Self {
gamenight_database::user::LoginUser {
username: val.username,
password: val.password
}
}
}
impl From<Registration> for gamenight_database::user::Register {
fn from(val: Registration) -> Self {
gamenight_database::user::Register {
email: val.email,
username: val.username,
password: val.password
}
}
}
pub struct RegisterContext<'v_a> {
pub pool: &'v_a DbPool
}
pub fn unique_username(username: &String, context: &RegisterContext) -> Result<(), ValidationError> {
let mut conn = context.pool.get_conn();
match count_users_with_username(&mut conn, username)
{
Ok(0) => Ok(()),
Ok(_) => Err(ValidationError::new("User already exists")),
Err(_) => Err(ValidationError::new("Database error while validating user")),
}
}
pub fn unique_email(email: &String, context: &RegisterContext) -> Result<(), ValidationError> {
let mut conn = context.pool.get_conn();
match count_users_with_email(&mut conn, email)
{
Ok(0) => Ok(()),
Ok(_) => Err(ValidationError::new("email already exists")),
Err(_) => Err(ValidationError::new("Database error while validating email"))
}
}
#[derive(Serialize, Deserialize, Clone, Validate)]
#[validate(context = RegisterContext::<'v_a>)]
pub struct ValidatableRegistration {
#[validate(
length(min = 1),
custom(function = "unique_username", use_context)
)]
pub username: String,
#[validate(
email,
custom(function = "unique_email", use_context)
)]
pub email: String,
#[validate(length(min = 10), must_match(other = "password_repeat", ))]
pub password: String,
pub password_repeat: String,
}
impl From<Registration> for ValidatableRegistration {
fn from(value: Registration) -> Self {
Self {
username: value.username,
email: value.email,
password: value.password,
password_repeat: value.password_repeat
}
}
}
#[get("/token")]
pub async fn login(pool: web::Data<DbPool>, login_data: web::Json<Login>) -> Result<impl Responder, ApiError> {
let data = login_data.into_inner();
if let Ok(Some(user)) = web::block(move || {
let mut conn = pool.get_conn();
gamenight_database::login(&mut conn, data.into())
})
.await?
{
let token = get_token(&user)?;
let response = Token{ jwt_token: Some(token) };
Ok(HttpResponse::Ok()
.content_type(ContentType::json())
.body(serde_json::to_string(&response)?))
}
else {
Err(ApiError{status: 401, message: "User doesn't exist or password doesn't match".to_string()})
}
}
#[post("/token")]
pub async fn refresh(user: AuthUser) -> Result<impl Responder, ApiError> {
let new_token = get_token(&user.0)?;
let response = Token{ jwt_token: Some(new_token) };
Ok(HttpResponse::Ok()
.content_type(ContentType::json())
.body(serde_json::to_string(&response)?))
}
#[post("/user")]
pub async fn register(pool: web::Data<DbPool>, register_data: web::Json<Registration>) -> Result<impl Responder, ApiError> {
web::block(move || -> Result<(), ApiError> {
let validatable_registration: ValidatableRegistration = register_data.clone().into();
validatable_registration.validate_with_args(&RegisterContext{pool: &pool})?;
let register_request = register_data.into_inner().into();
let mut conn = pool.get_conn();
gamenight_database::register(&mut conn, register_request)?;
Ok(())
}).await??;
Ok(HttpResponse::Ok())
}
impl From<gamenight_database::user::User> for User {
fn from(value: gamenight_database::user::User) -> Self {
Self {
id: value.id.to_string(),
username: value.username,
email: None,
}
}
}
#[get("/user")]
pub async fn get_user(pool: web::Data<DbPool>, _user: AuthUser, user_info: web::Json<UserId>) -> Result<impl Responder, ApiError> {
let mut conn = pool.get_conn();
let user = gamenight_database::user::get_user(&mut conn, Uuid::parse_str(&user_info.into_inner().user_id)?)?;
Ok(HttpResponse::Ok()
.content_type(ContentType::json())
.body(serde_json::to_string(&user)?))
}
#[get("/user")]
pub async fn get_user_unauthenticated(_path: web::Path<UserId>) -> Result<impl Responder, ApiError> {
Ok(HttpResponse::Forbidden())
}

18
docker-compose.yml Normal file
View File

@ -0,0 +1,18 @@
services:
db:
container_name: pg_container
image: postgres
environment:
POSTGRES_USER: root
POSTGRES_PASSWORD: root
POSTGRES_DB: gamenight
ports:
- "5432:5432"
pgadmin:
container_name: pgadmin4_container
image: dpage/pgadmin4
environment:
PGADMIN_DEFAULT_EMAIL: admin@admin.com
PGADMIN_DEFAULT_PASSWORD: root
ports:
- "5050:80"

3
gamenight-api-client-rs/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/target/
**/*.rs.bk
Cargo.lock

View File

@ -0,0 +1,23 @@
# OpenAPI Generator Ignore
# Generated by openapi-generator https://github.com/openapitools/openapi-generator
# Use this file to prevent files from being overwritten by the generator.
# The patterns follow closely to .gitignore or .dockerignore.
# As an example, the C# client generator defines ApiClient.cs.
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
#ApiClient.cs
# You can match any string of characters against a directory, file or extension with a single asterisk (*):
#foo/*/qux
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
#foo/**/qux
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
# You can also negate patterns with an exclamation (!).
# For example, you can ignore all files in a docs folder with the file extension .md:
#docs/*.md
# Then explicitly reverse the ignore rule for a single file:
#!docs/README.md

View File

@ -0,0 +1,39 @@
.gitignore
.travis.yml
Cargo.toml
README.md
docs/AddGameRequestBody.md
docs/AddGamenightRequestBody.md
docs/DefaultApi.md
docs/Failure.md
docs/Game.md
docs/GameId.md
docs/Gamenight.md
docs/GamenightId.md
docs/GetGamenightRequestBody.md
docs/Login.md
docs/Participants.md
docs/Registration.md
docs/Token.md
docs/User.md
docs/UserId.md
git_push.sh
src/apis/configuration.rs
src/apis/default_api.rs
src/apis/mod.rs
src/lib.rs
src/models/add_game_request_body.rs
src/models/add_gamenight_request_body.rs
src/models/failure.rs
src/models/game.rs
src/models/game_id.rs
src/models/gamenight.rs
src/models/gamenight_id.rs
src/models/get_gamenight_request_body.rs
src/models/login.rs
src/models/mod.rs
src/models/participants.rs
src/models/registration.rs
src/models/token.rs
src/models/user.rs
src/models/user_id.rs

View File

@ -0,0 +1 @@
7.13.0

View File

@ -0,0 +1 @@
language: rust

View File

@ -0,0 +1,14 @@
[package]
name = "gamenight-api-client-rs"
version = "0.1.0"
authors = ["dennis@brentj.es"]
description = "Api specifaction for a Gamenight server"
license = "MIT"
edition = "2021"
[dependencies]
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"] }

View File

@ -0,0 +1,72 @@
# Rust API client for gamenight-api-client-rs
Api specifaction for a Gamenight server
For more information, please visit [https://brentj.es](https://brentj.es)
## Overview
This API client was generated by the [OpenAPI Generator](https://openapi-generator.tech) project. By using the [openapi-spec](https://openapis.org) from a remote server, you can easily generate an API client.
- API version: 1.0
- Package version: 0.1.0
- Generator version: 7.13.0
- Build package: `org.openapitools.codegen.languages.RustClientCodegen`
## Installation
Put the package under your project folder in a directory named `gamenight-api-client-rs` and add the following to `Cargo.toml` under `[dependencies]`:
```
gamenight-api-client-rs = { path = "./gamenight-api-client-rs" }
```
## Documentation for API Endpoints
All URIs are relative to *http://localhost:8080*
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_gamenights**](docs/DefaultApi.md#get_gamenights) | **GET** /gamenights | Get a all gamenights
*DefaultApi* | [**get_token**](docs/DefaultApi.md#get_token) | **GET** /token |
*DefaultApi* | [**join_post**](docs/DefaultApi.md#join_post) | **POST** /join |
*DefaultApi* | [**leave_post**](docs/DefaultApi.md#leave_post) | **POST** /leave |
*DefaultApi* | [**participants_get**](docs/DefaultApi.md#participants_get) | **GET** /participants | Get all participants for a gamenight
*DefaultApi* | [**post_gamenight**](docs/DefaultApi.md#post_gamenight) | **POST** /gamenight |
*DefaultApi* | [**post_register**](docs/DefaultApi.md#post_register) | **POST** /user |
*DefaultApi* | [**post_token**](docs/DefaultApi.md#post_token) | **POST** /token |
*DefaultApi* | [**user_get**](docs/DefaultApi.md#user_get) | **GET** /user |
## Documentation For Models
- [AddGameRequestBody](docs/AddGameRequestBody.md)
- [AddGamenightRequestBody](docs/AddGamenightRequestBody.md)
- [Failure](docs/Failure.md)
- [Game](docs/Game.md)
- [GameId](docs/GameId.md)
- [Gamenight](docs/Gamenight.md)
- [GamenightId](docs/GamenightId.md)
- [GetGamenightRequestBody](docs/GetGamenightRequestBody.md)
- [Login](docs/Login.md)
- [Participants](docs/Participants.md)
- [Registration](docs/Registration.md)
- [Token](docs/Token.md)
- [User](docs/User.md)
- [UserId](docs/UserId.md)
To get access to the crate's generated documentation, use:
```
cargo doc --open
```
## Author
dennis@brentj.es

View File

@ -0,0 +1,10 @@
use std::process::Command;
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"])
.output()
.expect("Failed to generate models sources for the gamenight API");
}

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

@ -0,0 +1,12 @@
# AddGamenightRequestBody
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**name** | **String** | |
**datetime** | **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,390 @@
# \DefaultApi
All URIs are relative to *http://localhost:8080*
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_gamenights**](DefaultApi.md#get_gamenights) | **GET** /gamenights | Get a all gamenights
[**get_token**](DefaultApi.md#get_token) | **GET** /token |
[**join_post**](DefaultApi.md#join_post) | **POST** /join |
[**leave_post**](DefaultApi.md#leave_post) | **POST** /leave |
[**participants_get**](DefaultApi.md#participants_get) | **GET** /participants | Get all participants for a gamenight
[**post_gamenight**](DefaultApi.md#post_gamenight) | **POST** /gamenight |
[**post_register**](DefaultApi.md#post_register) | **POST** /user |
[**post_token**](DefaultApi.md#post_token) | **POST** /token |
[**user_get**](DefaultApi.md#user_get) | **GET** /user |
## 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
> models::Gamenight get_gamenight(get_gamenight_request_body)
### Parameters
Name | Type | Description | Required | Notes
------------- | ------------- | ------------- | ------------- | -------------
**get_gamenight_request_body** | Option<[**GetGamenightRequestBody**](GetGamenightRequestBody.md)> | | |
### Return type
[**models::Gamenight**](Gamenight.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)
## get_gamenights
> Vec<models::Gamenight> get_gamenights()
Get a all gamenights
Retrieve the list of gamenights on this gamenight server. Requires authorization.
### Parameters
This endpoint does not need any parameter.
### Return type
[**Vec<models::Gamenight>**](Gamenight.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_token
> models::Token get_token(login)
Submit your credentials to get a JWT-token to use with the rest of the api.
### Parameters
Name | Type | Description | Required | Notes
------------- | ------------- | ------------- | ------------- | -------------
**login** | Option<[**Login**](Login.md)> | | |
### Return type
[**models::Token**](Token.md)
### Authorization
No authorization required
### 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)
## join_post
> join_post(gamenight_id)
### Parameters
Name | Type | Description | Required | Notes
------------- | ------------- | ------------- | ------------- | -------------
**gamenight_id** | Option<[**GamenightId**](GamenightId.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)
## leave_post
> leave_post(gamenight_id)
### Parameters
Name | Type | Description | Required | Notes
------------- | ------------- | ------------- | ------------- | -------------
**gamenight_id** | Option<[**GamenightId**](GamenightId.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)
## participants_get
> models::Participants participants_get(gamenight_id)
Get all participants for a gamenight
Retrieve the participants of a single gamenight by id.
### Parameters
Name | Type | Description | Required | Notes
------------- | ------------- | ------------- | ------------- | -------------
**gamenight_id** | Option<[**GamenightId**](GamenightId.md)> | | |
### Return type
[**models::Participants**](Participants.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)
## post_gamenight
> post_gamenight(add_gamenight_request_body)
Add a gamenight by providing a name and a date, only available when providing an JWT token.
### Parameters
Name | Type | Description | Required | Notes
------------- | ------------- | ------------- | ------------- | -------------
**add_gamenight_request_body** | Option<[**AddGamenightRequestBody**](AddGamenightRequestBody.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)
## post_register
> post_register(registration)
Create a new user given a registration token and user information, username and email must be unique, and password and password_repeat must match.
### Parameters
Name | Type | Description | Required | Notes
------------- | ------------- | ------------- | ------------- | -------------
**registration** | Option<[**Registration**](Registration.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)
## post_token
> models::Token post_token()
Refresh your JWT-token without logging in again.
### Parameters
This endpoint does not need any parameter.
### Return type
[**models::Token**](Token.md)
### Authorization
[JWT-Auth](../README.md#JWT-Auth)
### HTTP request headers
- **Content-Type**: Not defined
- **Accept**: application/json
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
## user_get
> models::User user_get(user_id)
Get a user from primary id
### Parameters
Name | Type | Description | Required | Notes
------------- | ------------- | ------------- | ------------- | -------------
**user_id** | Option<[**UserId**](UserId.md)> | | |
### Return type
[**models::User**](User.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)

View File

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

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

@ -0,0 +1,14 @@
# Gamenight
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**id** | **String** | |
**name** | **String** | |
**datetime** | **String** | |
**owner_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

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

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

View File

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

View File

@ -0,0 +1,11 @@
# GetToken401Response
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**message** | **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,12 @@
# Login
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**username** | **String** | |
**password** | **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 @@
# Participants
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**participants** | **Vec<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,15 @@
# Registration
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**username** | **String** | |
**email** | **String** | |
**password** | **String** | |
**password_repeat** | **String** | |
**registration_token** | **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 @@
# Token
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**jwt_token** | 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)

View File

@ -0,0 +1,13 @@
# User
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**id** | **String** | |
**username** | **String** | |
**email** | 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)

View File

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

@ -0,0 +1,57 @@
#!/bin/sh
# ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/
#
# Usage example: /bin/sh ./git_push.sh wing328 openapi-petstore-perl "minor update" "gitlab.com"
git_user_id=$1
git_repo_id=$2
release_note=$3
git_host=$4
if [ "$git_host" = "" ]; then
git_host="github.com"
echo "[INFO] No command line input provided. Set \$git_host to $git_host"
fi
if [ "$git_user_id" = "" ]; then
git_user_id="GIT_USER_ID"
echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id"
fi
if [ "$git_repo_id" = "" ]; then
git_repo_id="GIT_REPO_ID"
echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id"
fi
if [ "$release_note" = "" ]; then
release_note="Minor update"
echo "[INFO] No command line input provided. Set \$release_note to $release_note"
fi
# Initialize the local directory as a Git repository
git init
# Adds the files in the local repository and stages them for commit.
git add .
# Commits the tracked changes and prepares them to be pushed to a remote repository.
git commit -m "$release_note"
# Sets the new remote
git_remote=$(git remote)
if [ "$git_remote" = "" ]; then # git remote not defined
if [ "$GIT_TOKEN" = "" ]; then
echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment."
git remote add origin https://${git_host}/${git_user_id}/${git_repo_id}.git
else
git remote add origin https://${git_user_id}:"${GIT_TOKEN}"@${git_host}/${git_user_id}/${git_repo_id}.git
fi
fi
git pull origin master
# Pushes (Forces) the changes in the local repository up to the remote repository
echo "Git pushing to https://${git_host}/${git_user_id}/${git_repo_id}.git"
git push origin master 2>&1 | grep -v 'To https'

View File

@ -0,0 +1,51 @@
/*
* 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
*/
#[derive(Debug, Clone)]
pub struct Configuration {
pub base_path: String,
pub user_agent: Option<String>,
pub client: reqwest::Client,
pub basic_auth: Option<BasicAuth>,
pub oauth_access_token: Option<String>,
pub bearer_access_token: Option<String>,
pub api_key: Option<ApiKey>,
}
pub type BasicAuth = (String, Option<String>);
#[derive(Debug, Clone)]
pub struct ApiKey {
pub prefix: Option<String>,
pub key: String,
}
impl Configuration {
pub fn new() -> Configuration {
Configuration::default()
}
}
impl Default for Configuration {
fn default() -> Self {
Configuration {
base_path: "http://localhost:8080".to_owned(),
user_agent: Some("OpenAPI-Generator/1.0/rust".to_owned()),
client: reqwest::Client::new(),
basic_auth: None,
oauth_access_token: None,
bearer_access_token: None,
api_key: None,
}
}
}

View File

@ -0,0 +1,593 @@
/*
* 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 reqwest;
use serde::{Deserialize, Serialize, de::Error as _};
use crate::{apis::ResponseContent, models};
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`]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum GetGamenightError {
Status401(models::Failure),
Status422(models::Failure),
UnknownValue(serde_json::Value),
}
/// struct for typed errors of method [`get_gamenights`]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum GetGamenightsError {
Status400(models::Failure),
Status401(models::Failure),
UnknownValue(serde_json::Value),
}
/// struct for typed errors of method [`get_token`]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum GetTokenError {
Status401(models::Failure),
UnknownValue(serde_json::Value),
}
/// struct for typed errors of method [`join_post`]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum JoinPostError {
Status401(models::Failure),
Status422(models::Failure),
UnknownValue(serde_json::Value),
}
/// struct for typed errors of method [`leave_post`]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum LeavePostError {
Status401(models::Failure),
Status422(models::Failure),
UnknownValue(serde_json::Value),
}
/// struct for typed errors of method [`participants_get`]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ParticipantsGetError {
Status400(models::Failure),
Status401(models::Failure),
UnknownValue(serde_json::Value),
}
/// struct for typed errors of method [`post_gamenight`]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum PostGamenightError {
Status401(models::Failure),
Status422(models::Failure),
UnknownValue(serde_json::Value),
}
/// struct for typed errors of method [`post_register`]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum PostRegisterError {
Status422(models::Failure),
UnknownValue(serde_json::Value),
}
/// struct for typed errors of method [`post_token`]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum PostTokenError {
Status401(models::Failure),
UnknownValue(serde_json::Value),
}
/// struct for typed errors of method [`user_get`]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum UserGetError {
Status401(models::Failure),
Status404(models::Failure),
Status422(models::Failure),
UnknownValue(serde_json::Value),
}
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>> {
// add a prefix to parameters to efficiently prevent name collisions
let p_get_gamenight_request_body = get_gamenight_request_body;
let uri_str = format!("{}/gamenight", 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_get_gamenight_request_body);
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::Gamenight`"))),
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::Gamenight`")))),
}
} else {
let content = resp.text().await?;
let entity: Option<GetGamenightError> = serde_json::from_str(&content).ok();
Err(Error::ResponseError(ResponseContent { status, content, entity }))
}
}
/// Retrieve the list of gamenights on this gamenight server. Requires authorization.
pub async fn get_gamenights(configuration: &configuration::Configuration, ) -> Result<Vec<models::Gamenight>, Error<GetGamenightsError>> {
let uri_str = format!("{}/gamenights", 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::Gamenight&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::Gamenight&gt;`")))),
}
} else {
let content = resp.text().await?;
let entity: Option<GetGamenightsError> = serde_json::from_str(&content).ok();
Err(Error::ResponseError(ResponseContent { status, content, entity }))
}
}
/// Submit your credentials to get a JWT-token to use with the rest of the api.
pub async fn get_token(configuration: &configuration::Configuration, login: Option<models::Login>) -> Result<models::Token, Error<GetTokenError>> {
// add a prefix to parameters to efficiently prevent name collisions
let p_login = login;
let uri_str = format!("{}/token", 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());
}
req_builder = req_builder.json(&p_login);
let req = req_builder.build()?;
let resp = configuration.client.execute(req).await?;
let status = resp.status();
let content_type = resp
.headers()
.get("content-type")
.and_then(|v| v.to_str().ok())
.unwrap_or("application/octet-stream");
let content_type = super::ContentType::from(content_type);
if !status.is_client_error() && !status.is_server_error() {
let content = resp.text().await?;
match content_type {
ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::Token`"))),
ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::Token`")))),
}
} else {
let content = resp.text().await?;
let entity: Option<GetTokenError> = serde_json::from_str(&content).ok();
Err(Error::ResponseError(ResponseContent { status, content, entity }))
}
}
pub async fn join_post(configuration: &configuration::Configuration, gamenight_id: Option<models::GamenightId>) -> Result<(), Error<JoinPostError>> {
// add a prefix to parameters to efficiently prevent name collisions
let p_gamenight_id = gamenight_id;
let uri_str = format!("{}/join", 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_gamenight_id);
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<JoinPostError> = serde_json::from_str(&content).ok();
Err(Error::ResponseError(ResponseContent { status, content, entity }))
}
}
pub async fn leave_post(configuration: &configuration::Configuration, gamenight_id: Option<models::GamenightId>) -> Result<(), Error<LeavePostError>> {
// add a prefix to parameters to efficiently prevent name collisions
let p_gamenight_id = gamenight_id;
let uri_str = format!("{}/leave", 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_gamenight_id);
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<LeavePostError> = serde_json::from_str(&content).ok();
Err(Error::ResponseError(ResponseContent { status, content, entity }))
}
}
/// Retrieve the participants of a single gamenight by id.
pub async fn participants_get(configuration: &configuration::Configuration, gamenight_id: Option<models::GamenightId>) -> Result<models::Participants, Error<ParticipantsGetError>> {
// add a prefix to parameters to efficiently prevent name collisions
let p_gamenight_id = gamenight_id;
let uri_str = format!("{}/participants", 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_gamenight_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::Participants`"))),
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::Participants`")))),
}
} else {
let content = resp.text().await?;
let entity: Option<ParticipantsGetError> = serde_json::from_str(&content).ok();
Err(Error::ResponseError(ResponseContent { status, content, entity }))
}
}
/// Add a gamenight by providing a name and a date, only available when providing an JWT token.
pub async fn post_gamenight(configuration: &configuration::Configuration, add_gamenight_request_body: Option<models::AddGamenightRequestBody>) -> Result<(), Error<PostGamenightError>> {
// add a prefix to parameters to efficiently prevent name collisions
let p_add_gamenight_request_body = add_gamenight_request_body;
let uri_str = format!("{}/gamenight", 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_gamenight_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<PostGamenightError> = serde_json::from_str(&content).ok();
Err(Error::ResponseError(ResponseContent { status, content, entity }))
}
}
/// Create a new user given a registration token and user information, username and email must be unique, and password and password_repeat must match.
pub async fn post_register(configuration: &configuration::Configuration, registration: Option<models::Registration>) -> Result<(), Error<PostRegisterError>> {
// add a prefix to parameters to efficiently prevent name collisions
let p_registration = registration;
let uri_str = format!("{}/user", 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_registration);
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<PostRegisterError> = serde_json::from_str(&content).ok();
Err(Error::ResponseError(ResponseContent { status, content, entity }))
}
}
/// Refresh your JWT-token without logging in again.
pub async fn post_token(configuration: &configuration::Configuration, ) -> Result<models::Token, Error<PostTokenError>> {
let uri_str = format!("{}/token", configuration.base_path);
let mut req_builder = configuration.client.request(reqwest::Method::POST, &uri_str);
if let Some(ref user_agent) = configuration.user_agent {
req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
}
if let Some(ref token) = configuration.bearer_access_token {
req_builder = req_builder.bearer_auth(token.to_owned());
};
let req = req_builder.build()?;
let resp = configuration.client.execute(req).await?;
let status = resp.status();
let content_type = resp
.headers()
.get("content-type")
.and_then(|v| v.to_str().ok())
.unwrap_or("application/octet-stream");
let content_type = super::ContentType::from(content_type);
if !status.is_client_error() && !status.is_server_error() {
let content = resp.text().await?;
match content_type {
ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::Token`"))),
ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::Token`")))),
}
} else {
let content = resp.text().await?;
let entity: Option<PostTokenError> = serde_json::from_str(&content).ok();
Err(Error::ResponseError(ResponseContent { status, content, entity }))
}
}
/// Get a user from primary id
pub async fn user_get(configuration: &configuration::Configuration, user_id: Option<models::UserId>) -> Result<models::User, Error<UserGetError>> {
// add a prefix to parameters to efficiently prevent name collisions
let p_user_id = user_id;
let uri_str = format!("{}/user", 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_user_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::User`"))),
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::User`")))),
}
} else {
let content = resp.text().await?;
let entity: Option<UserGetError> = serde_json::from_str(&content).ok();
Err(Error::ResponseError(ResponseContent { status, content, entity }))
}
}

View File

@ -0,0 +1,116 @@
use std::error;
use std::fmt;
#[derive(Debug, Clone)]
pub struct ResponseContent<T> {
pub status: reqwest::StatusCode,
pub content: String,
pub entity: Option<T>,
}
#[derive(Debug)]
pub enum Error<T> {
Reqwest(reqwest::Error),
Serde(serde_json::Error),
Io(std::io::Error),
ResponseError(ResponseContent<T>),
}
impl <T> fmt::Display for Error<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let (module, e) = match self {
Error::Reqwest(e) => ("reqwest", e.to_string()),
Error::Serde(e) => ("serde", e.to_string()),
Error::Io(e) => ("IO", e.to_string()),
Error::ResponseError(e) => ("response", format!("status code {}", e.status)),
};
write!(f, "error in {}: {}", module, e)
}
}
impl <T: fmt::Debug> error::Error for Error<T> {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
Some(match self {
Error::Reqwest(e) => e,
Error::Serde(e) => e,
Error::Io(e) => e,
Error::ResponseError(_) => return None,
})
}
}
impl <T> From<reqwest::Error> for Error<T> {
fn from(e: reqwest::Error) -> Self {
Error::Reqwest(e)
}
}
impl <T> From<serde_json::Error> for Error<T> {
fn from(e: serde_json::Error) -> Self {
Error::Serde(e)
}
}
impl <T> From<std::io::Error> for Error<T> {
fn from(e: std::io::Error) -> Self {
Error::Io(e)
}
}
pub fn urlencode<T: AsRef<str>>(s: T) -> String {
::url::form_urlencoded::byte_serialize(s.as_ref().as_bytes()).collect()
}
pub fn parse_deep_object(prefix: &str, value: &serde_json::Value) -> Vec<(String, String)> {
if let serde_json::Value::Object(object) = value {
let mut params = vec![];
for (key, value) in object {
match value {
serde_json::Value::Object(_) => params.append(&mut parse_deep_object(
&format!("{}[{}]", prefix, key),
value,
)),
serde_json::Value::Array(array) => {
for (i, value) in array.iter().enumerate() {
params.append(&mut parse_deep_object(
&format!("{}[{}][{}]", prefix, key, i),
value,
));
}
},
serde_json::Value::String(s) => params.push((format!("{}[{}]", prefix, key), s.clone())),
_ => params.push((format!("{}[{}]", prefix, key), value.to_string())),
}
}
return params;
}
unimplemented!("Only objects are supported with style=deepObject")
}
/// Internal use only
/// A content type supported by this client.
#[allow(dead_code)]
enum ContentType {
Json,
Text,
Unsupported(String)
}
impl From<&str> for ContentType {
fn from(content_type: &str) -> Self {
if content_type.starts_with("application") && content_type.contains("json") {
return Self::Json;
} else if content_type.starts_with("text/plain") {
return Self::Text;
} else {
return Self::Unsupported(content_type.to_string());
}
}
}
pub mod default_api;
pub mod configuration;

View File

@ -0,0 +1,11 @@
#![allow(unused_imports)]
#![allow(clippy::too_many_arguments)]
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

@ -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 AddGamenightRequestBody {
#[serde(rename = "name")]
pub name: String,
#[serde(rename = "datetime")]
pub datetime: String,
}
impl AddGamenightRequestBody {
pub fn new(name: String, datetime: String) -> AddGamenightRequestBody {
AddGamenightRequestBody {
name,
datetime,
}
}
}

View File

@ -0,0 +1,29 @@
/*
* 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};
/// Failure : Failure Reason
#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
pub struct Failure {
#[serde(rename = "message", skip_serializing_if = "Option::is_none")]
pub message: Option<String>,
}
impl Failure {
/// Failure Reason
pub fn new() -> Failure {
Failure {
message: None,
}
}
}

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

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

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 GamenightId {
#[serde(rename = "gamenight_id")]
pub gamenight_id: String,
}
impl GamenightId {
pub fn new(gamenight_id: String) -> GamenightId {
GamenightId {
gamenight_id,
}
}
}

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 GetGamenightRequest {
#[serde(rename = "id", skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
}
impl GetGamenightRequest {
pub fn new() -> GetGamenightRequest {
GetGamenightRequest {
id: None,
}
}
}

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 GetGamenightRequestBody {
#[serde(rename = "id", skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
}
impl GetGamenightRequestBody {
pub fn new() -> GetGamenightRequestBody {
GetGamenightRequestBody {
id: None,
}
}
}

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 GetToken401Response {
#[serde(rename = "message")]
pub message: String,
}
impl GetToken401Response {
pub fn new(message: String) -> GetToken401Response {
GetToken401Response {
message,
}
}
}

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 Login {
#[serde(rename = "username")]
pub username: String,
#[serde(rename = "password")]
pub password: String,
}
impl Login {
pub fn new(username: String, password: String) -> Login {
Login {
username,
password,
}
}
}

View File

@ -0,0 +1,28 @@
pub mod add_game_request_body;
pub use self::add_game_request_body::AddGameRequestBody;
pub mod add_gamenight_request_body;
pub use self::add_gamenight_request_body::AddGamenightRequestBody;
pub mod 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 use self::gamenight::Gamenight;
pub mod gamenight_id;
pub use self::gamenight_id::GamenightId;
pub mod get_gamenight_request_body;
pub use self::get_gamenight_request_body::GetGamenightRequestBody;
pub mod login;
pub use self::login::Login;
pub mod participants;
pub use self::participants::Participants;
pub mod registration;
pub use self::registration::Registration;
pub mod token;
pub use self::token::Token;
pub mod user;
pub use self::user::User;
pub mod user_id;
pub use self::user_id::UserId;

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 Participants {
#[serde(rename = "participants")]
pub participants: Vec<String>,
}
impl Participants {
pub fn new(participants: Vec<String>) -> Participants {
Participants {
participants,
}
}
}

View File

@ -0,0 +1,39 @@
/*
* 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 Registration {
#[serde(rename = "username")]
pub username: String,
#[serde(rename = "email")]
pub email: String,
#[serde(rename = "password")]
pub password: String,
#[serde(rename = "password_repeat")]
pub password_repeat: String,
#[serde(rename = "registration_token")]
pub registration_token: String,
}
impl Registration {
pub fn new(username: String, email: String, password: String, password_repeat: String, registration_token: String) -> Registration {
Registration {
username,
email,
password,
password_repeat,
registration_token,
}
}
}

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 Token {
#[serde(rename = "jwt_token", skip_serializing_if = "Option::is_none")]
pub jwt_token: Option<String>,
}
impl Token {
pub fn new() -> Token {
Token {
jwt_token: None,
}
}
}

View File

@ -0,0 +1,33 @@
/*
* 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 User {
#[serde(rename = "id")]
pub id: String,
#[serde(rename = "username")]
pub username: String,
#[serde(rename = "email", skip_serializing_if = "Option::is_none")]
pub email: Option<String>,
}
impl User {
pub fn new(id: String, username: String) -> User {
User {
id,
username,
email: None,
}
}
}

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 UserId {
#[serde(rename = "user_id")]
pub user_id: String,
}
impl UserId {
pub fn new(user_id: String) -> UserId {
UserId {
user_id,
}
}
}

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

@ -0,0 +1 @@
target

1723
gamenight-cli/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

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

@ -0,0 +1,17 @@
[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 = { version = "0.7.5", features = ["date"] }
async-trait = "0.1"
dyn-clone = "1.0"
chrono = "0.4"
uuid = { version = "1.3.0", features = ["serde", "v4"] }
jsonwebtoken = "9.3"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
clear_screen = "0.1"

View File

@ -0,0 +1,85 @@
use std::{env, fs::{self}, path::{Path, PathBuf}};
use serde::{Deserialize, Serialize};
#[derive(Debug)]
pub struct ConfigError(pub String);
impl From<serde_json::Error> for ConfigError {
fn from(value: serde_json::Error) -> Self {
Self(value.to_string())
}
}
impl From<std::io::Error> for ConfigError {
fn from(value: std::io::Error) -> Self {
Self(value.to_string())
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Instance {
pub name: String,
pub url: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub token: Option<String>
}
impl Instance {
pub fn new(name: String, url: String) -> Self {
Self {
name,
url,
token: None
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Config {
pub instances: Vec<Instance>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub last_instance: Option<String>,
}
impl Config {
fn config_path() -> PathBuf {
let mut prefix = Path::new(&env::var("HOME").expect("HOME environment variable was not set")).join(".config");
if let Ok(config_home) = env::var("XDG_CONFIG_HOME") {
prefix = config_home.into();
}
prefix.join("gamenight-cli").join("config.json")
}
pub fn new() -> Config {
Config {
instances: vec![],
last_instance: None
}
}
pub fn load() -> Result<Config, ConfigError> {
let config_path = Self::config_path();
if !fs::exists(&config_path)? {
let config_error = ConfigError(format!("Cannot create parent directory for config file: {}", &config_path.display()).to_string());
let _ = fs::create_dir_all(&config_path.parent().ok_or(config_error)?)?;
let config = Config::new();
fs::write(&config_path, serde_json::to_string_pretty(&config)?.as_bytes())?;
Ok(config)
}
else {
let config_string = fs::read_to_string(Self::config_path())?;
Ok(serde_json::from_str(&config_string)?)
}
}
pub fn save(gamenight_configuration: &Config) -> Result<(), ConfigError> {
let config_path = Self::config_path();
fs::write(&config_path, serde_json::to_string_pretty(gamenight_configuration)?.as_bytes())?;
Ok(())
}
}

View File

@ -0,0 +1,19 @@
use std::fmt::Display;
use chrono::{DateTime, Local};
use uuid::Uuid;
#[derive(Clone)]
pub struct Gamenight {
pub id: Uuid,
pub name: String,
pub start_time: DateTime<Local>,
pub owner_id: Uuid,
}
impl Display for Gamenight {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, r#"Name: {}
When: {}"#, self.name, self.start_time.format("%d-%m-%Y %H:%M"))
}
}

View File

@ -0,0 +1,4 @@
pub mod gamenight;
pub mod user;
pub mod config;
pub mod participants;

View File

@ -0,0 +1,12 @@
use std::fmt::Display;
use crate::domain::user::User;
pub struct Participants<'a>(pub &'a Vec<User>);
impl<'a> Display for Participants<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let string_list: Vec<String> = self.0.iter().map(|x| {format!("{}", x)}).collect();
write!(f, "{}", string_list.join(", "))
}
}

View File

@ -0,0 +1,16 @@
use std::fmt::Display;
use uuid::Uuid;
#[derive(Clone)]
pub struct User {
pub id: Uuid,
pub username: String
}
impl Display for User {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.username)
}
}

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,51 @@
use gamenight_api_client_rs::{apis::default_api::post_gamenight, models};
use inquire::{CustomType, DateSelect, Text};
use chrono::{self, Local, NaiveTime};
use super::*;
#[derive(Clone)]
pub struct AddGamenight {
}
impl AddGamenight {
pub fn new() -> Self {
Self {}
}
}
#[async_trait]
impl<'a> Flow<'a> for AddGamenight {
async fn run(&self, state: &'a mut GamenightState) -> FlowResult<'a> {
if let Some(name) = Text::new("What should we call your gamenight").prompt_skippable()? {
if let Some(naive_date) = DateSelect::new("When is your gamenight").prompt_skippable()? {
if let Some(naive_time) = CustomType::<NaiveTime>::new("At What time").prompt_skippable()? {
let datetime = naive_date
.and_time(naive_time)
.and_local_timezone(Local)
.earliest()
.unwrap()
.to_utc()
.to_rfc3339();
let add_gamenight = models::AddGamenightRequestBody::new(name, datetime);
post_gamenight(&state.api_configuration, Some(add_gamenight)).await?;
clear_screen::clear();
return Ok((FlowOutcome::Successful, state))
}
}
}
Ok((FlowOutcome::Cancelled, state))
}
}
impl Display for AddGamenight {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Add Gamenight.")
}
}

View File

@ -0,0 +1,139 @@
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};
#[derive(Clone)]
pub struct Connect {
instance: Option<Instance>
}
impl Connect {
pub fn to(instance: Instance) -> Self {
Self {
instance: Some(instance)
}
}
pub fn new() -> Self {
Self {
instance: None
}
}
pub fn prompt_new_instance(&self, config: &Config) -> Result<Option<Instance>, FlowError> {
let instance_name: String;
loop {
if let Some(name) = Text::new("Name this instance:").prompt_skippable()? {
if config.instances.iter().find(|x| x.name == name).is_none() {
instance_name = name;
break;
} else {
clear_screen::clear();
println!("Name already in use, please provide a unique name");
}
}
else {
return Ok(None);
}
}
if let Some(url) = Text::new("What is the server URL: ").prompt_skippable()? {
Ok(Some(Instance::new(instance_name, url)))
} else {
Ok(None)
}
}
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> {
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;
if let Ok(token) = result {
let instance = config.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)
}
else {
Ok(false)
}
} else {
Ok(false)
}
}
pub fn update_state_on_logon(&self, instance: &mut Instance, api_configuration: &mut Configuration, config: &mut Config, 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());
}
else {
let instance = config.instances.iter_mut().find(|x| x.name == *instance_name).unwrap();
instance.token = Some(api_configuration.bearer_access_token.clone().unwrap());
}
config.last_instance = Some(instance_name.clone());
Config::save(&config)?;
Ok(())
}
}
#[async_trait]
impl<'a> Flow<'a> for Connect {
async fn run(&self, state: &'a mut GamenightState) -> FlowResult<'a> {
let mut instance = if let Some(instance) = self.instance.clone() {
instance
} else {
if let Some(instance) = self.prompt_new_instance(&state.gamenight_configuration)? {
instance
}
else {
return Ok((FlowOutcome::Cancelled, state));
}
};
let instance_name = instance.name.clone();
state.api_configuration.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 gamenight_menu_flow = GamenightMenu::new();
gamenight_menu_flow.run(state).await
}
else {
let login_flow = Login::new();
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)?;
let gamenight_menu_flow = GamenightMenu::new();
gamenight_menu_flow.run(state).await
}
else {
Ok((outcome, state))
}
}
}
}
impl Display for Connect {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(instance) = &self.instance {
write!(f, "Connect to: {}", instance.name)
} else {
write!(f, "Connect")
}
}
}

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

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

View File

@ -0,0 +1,50 @@
use inquire::{ui::RenderConfig, Select};
use crate::flows::{add_gamenight::AddGamenight, exit::Exit, games::Games, list_gamenights::ListGamenights};
use super::*;
#[derive(Clone)]
pub struct GamenightMenu {
}
impl GamenightMenu {
pub fn new() -> Self {
Self {}
}
}
unsafe impl Send for GamenightMenu {
}
#[async_trait]
impl<'a> Flow<'a> for GamenightMenu {
async fn run(&self, state: &'a mut GamenightState) -> FlowResult<'a> {
let flows: Vec<Box<dyn Flow + Send>> = vec![
Box::new(ListGamenights::new()),
Box::new(AddGamenight::new()),
Box::new(Games::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 GamenightMenu {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Main menu")
}
}

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,36 @@
use std::fmt::Display;
use async_trait::async_trait;
use gamenight_api_client_rs::{apis::default_api::join_post, models::GamenightId};
use uuid::Uuid;
use super::{Flow, FlowOutcome, FlowResult, GamenightState};
#[derive(Clone)]
pub struct Join {
gamenight_id: Uuid
}
impl Join {
pub fn new(gamenight_id: Uuid) -> Self {
Self {
gamenight_id
}
}
}
#[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?;
clear_screen::clear();
Ok((FlowOutcome::Successful, state))
}
}
impl Display for Join {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Join")
}
}

View File

@ -0,0 +1,36 @@
use std::fmt::Display;
use async_trait::async_trait;
use gamenight_api_client_rs::{apis::default_api::leave_post, models::GamenightId};
use uuid::Uuid;
use super::{Flow, FlowOutcome, FlowResult, GamenightState};
#[derive(Clone)]
pub struct Leave {
gamenight_id: Uuid
}
impl Leave {
pub fn new(gamenight_id: Uuid) -> Self {
Self {
gamenight_id
}
}
}
#[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?;
clear_screen::clear();
Ok((FlowOutcome::Successful, state))
}
}
impl Display for Leave {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Leave")
}
}

View File

@ -0,0 +1,52 @@
use chrono::DateTime;
use gamenight_api_client_rs::apis::default_api::get_gamenights;
use inquire::Select;
use uuid::Uuid;
use crate::{domain::{gamenight::Gamenight}, flows::view_gamenight::ViewGamenight};
use super::{exit::Exit, *};
#[derive(Clone)]
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.api_configuration).await?;
let mut view_flows: Vec<Box<dyn Flow<'_> + Send>> = vec![];
for response in response.iter() {
let gamenight = Gamenight {
id: Uuid::parse_str(&response.id)?,
name: response.name.clone(),
start_time:DateTime::parse_from_rfc3339(&response.datetime)?.into(),
owner_id: Uuid::parse_str(&response.owner_id)?
};
view_flows.push(Box::new(ViewGamenight::new(gamenight)));
}
view_flows.push(Box::new(Exit::new()));
let choice = Select::new("What gamenight would you like to view?", view_flows).prompt_skippable()?;
clear_screen::clear();
handle_choice_option(&choice, self, state).await
}
}
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,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

@ -0,0 +1,46 @@
use async_trait::async_trait;
use gamenight_api_client_rs::{apis::{configuration::Configuration, default_api::get_token}, models};
use inquire::{Password, Text};
use super::*;
#[derive(Clone)]
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?;
clear_screen::clear();
if let Some(token) = result.jwt_token {
state.api_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,62 @@
use std::fmt::Display;
use inquire::{ui::RenderConfig, Select};
use crate::flows::{connect::Connect, exit::Exit, settings::Settings};
use super::*;
#[derive(Clone)]
pub struct Main {
}
impl Main {
pub fn new() -> Self {
Self {}
}
}
impl Default for Main {
fn default() -> Self {
Self::new()
}
}
#[async_trait]
impl<'a> Flow<'a> for Main {
async fn run(&self, state: &'a mut GamenightState) -> FlowResult<'a> {
let config = Config::load()?;
state.gamenight_configuration = config;
let mut choices: Vec<Box<dyn Flow<'a> + Send>> = vec![];
if let Some(last_instance) = &state.gamenight_configuration.last_instance {
if let Some(instance) = state.gamenight_configuration.instances.iter().find(|instance| {instance.name.eq(last_instance)}) {
choices.push(Box::new(Connect::to(instance.clone())));
}
}
choices.push(Box::new(Connect::new()));
choices.push(Box::new(Settings::new()));
choices.push(Box::new(Exit::new()));
let choice = Select::new("What would you like to do?", choices)
.with_help_message("Select the action you want to take or quit the program")
.with_render_config(RenderConfig {
option_index_prefix: inquire::ui::IndexPrefix::Simple,
..Default::default()
})
.prompt_skippable()?;
clear_screen::clear();
handle_choice_option(&choice, self, state).await
}
}
impl Display for Main {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "main")
}
}

View File

@ -0,0 +1,135 @@
use std::{fmt::Display, num::ParseIntError};
use async_trait::async_trait;
use chrono::ParseError;
use gamenight_api_client_rs::apis::{configuration::Configuration, Error};
use inquire::InquireError;
use dyn_clone::DynClone;
pub use clear_screen;
use crate::domain::config::{Config, ConfigError};
pub mod main;
mod login;
mod gamenight_menu;
mod exit;
mod list_gamenights;
mod add_gamenight;
mod view_gamenight;
mod join;
mod leave;
mod connect;
mod settings;
mod games;
mod list_games;
mod add_game;
mod edit_game;
pub struct GamenightState {
api_configuration: Configuration,
gamenight_configuration: Config,
}
impl GamenightState {
pub fn new() -> Self{
Self {
api_configuration: Configuration::new(),
gamenight_configuration: Config::new()
}
}
}
impl Default for GamenightState {
fn default() -> Self {
Self::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 From<ParseError> for FlowError {
fn from(value: ParseError) -> Self {
Self {
error: value.to_string()
}
}
}
impl<T> From<Error<T>> for FlowError {
fn from(value: Error<T>) -> Self {
Self {
error: value.to_string()
}
}
}
impl From<uuid::Error> for FlowError {
fn from(value: uuid::Error) -> Self {
Self {
error: value.to_string()
}
}
}
impl From<ConfigError> for FlowError {
fn from(value: ConfigError) -> Self {
Self {
error: value.0
}
}
}
impl From<ParseIntError> for FlowError {
fn from(value: ParseIntError) -> Self {
Self {
error: value.to_string()
}
}
}
#[derive(PartialEq)]
pub enum FlowOutcome {
Successful,
Cancelled,
Abort
}
type FlowResult<'a> = Result<(FlowOutcome, &'a mut GamenightState), FlowError>;
dyn_clone::clone_trait_object!(for<'a> Flow<'a>);
#[async_trait]
pub trait Flow<'a>: Sync + DynClone + Send + Display {
async fn run(&self, state: &'a mut GamenightState) -> FlowResult<'a>;
}
async fn handle_choice<'a>(choice: &Box<dyn Flow<'a> + Send>, flow: &dyn Flow<'a>, state: &'a mut GamenightState) -> FlowResult<'a> {
let (outcome, new_state) = choice.run(state).await?;
if outcome == FlowOutcome::Abort {
Ok((FlowOutcome::Successful, new_state))
}
else {
flow.run(new_state).await
}
}
async fn handle_choice_option<'a>(choice: &Option<Box<dyn Flow<'a> + Send>>, flow: &dyn Flow<'a>, state: &'a mut GamenightState) -> FlowResult<'a> {
if let Some(choice) = choice {
handle_choice(choice, flow, state).await
}
else {
Ok((FlowOutcome::Cancelled, state))
}
}

View File

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

View File

@ -0,0 +1,90 @@
use gamenight_api_client_rs::{apis::default_api::{participants_get, user_get}, models::{GamenightId, UserId}};
use inquire::Select;
use jsonwebtoken::{decode, DecodingKey, Validation};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::{domain::{gamenight::Gamenight, participants::Participants, user::User}, flows::{exit::Exit, join::Join, leave::Leave}};
use super::*;
#[derive(Clone)]
pub struct ViewGamenight {
gamenight: Gamenight
}
impl ViewGamenight {
pub fn new(gamenight: Gamenight) -> Self {
Self {
gamenight
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Claims {
exp: i64,
uid: Uuid
}
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> {
let participants = participants_get(&state.api_configuration, Some(GamenightId{gamenight_id: self.gamenight.id.to_string()})).await?;
let mut users = vec![];
for participant in participants.participants.iter() {
let user = user_get(&state.api_configuration, Some(UserId{user_id: participant.clone()})).await?;
users.push(User {
id: Uuid::parse_str(&user.id)?,
username: user.username
});
}
println!("{}\nwho: {}", self.gamenight, Participants(&users));
let decoding_key = DecodingKey::from_secret(b"");
let mut validation = Validation::default();
validation.insecure_disable_signature_validation();
let claims = decode::<Claims>(
&state.api_configuration.bearer_access_token.as_ref().unwrap(),
&decoding_key,
&validation)?.claims;
let join_or_leave: Box<dyn Flow<'a> + Send> =
if users.iter().map(|x| {x.id}).find(|x| *x == claims.uid) != None {
Box::new(Leave::new(self.gamenight.id))
}
else {
Box::new(Join::new(self.gamenight.id))
};
let options: Vec<Box<dyn Flow<'a> + Send>> = vec![
join_or_leave,
Box::new(Exit::new())
];
let choice = Select::new("What do you want to do:", options)
.prompt_skippable()?;
clear_screen::clear();
handle_choice_option(&choice, self, state).await
}
}
impl Display for ViewGamenight {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} {}", self.gamenight.name, self.gamenight.start_time.format("%d-%m-%Y %H:%M"))
}
}

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

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

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

@ -0,0 +1 @@
target

View File

@ -0,0 +1,15 @@
[package]
name = "gamenight-database"
version = "0.1.0"
edition = "2024"
[dependencies]
diesel = { version = "2.0", features = ["postgres", "r2d2", "uuid", "chrono"] }
diesel-derive-enum = { version = "2.0", features = ["postgres"] }
diesel_migrations = "2.0"
argon2 = { version = "0.5", features = ["std"] }
uuid = { version = "1.17.0", features = ["serde", "v4"] }
serde = { version = "1.0", features = ["derive"] }
chrono = { version = "0.4", features = ["serde"] }
rand_core = "0.9"

View File

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

View File

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

View File

@ -0,0 +1,12 @@
-- Your SQL goes here
CREATE TABLE gamenight (
id UUID NOT NULL PRIMARY KEY,
name VARCHAR NOT NULL,
datetime VARCHAR NOT NULL
);
CREATE TABLE game (
id UUID NOT NULL PRIMARY KEY,
name VARCHAR UNIQUE NOT NULL
);

View File

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

View File

@ -0,0 +1,28 @@
CREATE TYPE Role AS ENUM ('user', 'admin');
CREATE TABLE client (
id UUID NOT NULL PRIMARY KEY,
username VARCHAR UNIQUE NOT NULL,
email VARCHAR UNIQUE NOT NULL,
role Role NOT NULL
);
CREATE TABLE pwd (
user_id UUID NOT NULL PRIMARY KEY,
password VARCHAR NOT NULL,
CONSTRAINT FK_UserId FOREIGN KEY (user_id) REFERENCES client(id) ON DELETE CASCADE
);
--Initialize default admin user, with password "gamenight!"
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
DO $$
DECLARE
admin_uuid uuid = uuid_generate_v4();
BEGIN
INSERT INTO client (id, username, email, role)
values(admin_uuid, 'admin', '', 'admin');
insert INTO pwd (user_id, password)
values(admin_uuid, '$argon2id$v=19$m=4096,t=3,p=1$zEdUjCAnZqd8DziYWzlFHw$YBLQhKvYIZBY43B8zM6hyBvLKuqTeh0EM5pKOfbWQSI');
END $$;

Some files were not shown because too many files have changed in this diff Show More