Skip to content

Commit

Permalink
Add game connect route
Browse files Browse the repository at this point in the history
  • Loading branch information
SirLynix committed Jul 14, 2024
1 parent f760072 commit cc1219d
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 36 deletions.
47 changes: 43 additions & 4 deletions src/game_connection.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,56 @@
use actix_web::{post, web, HttpResponse, Responder};
use deadpool_postgres::tokio_postgres::types::Type;
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use crate::{
app_data::AppData,
errors::api::RouteError,
errors::api::{ErrorCode, RequestError, RouteError},
game_connection_token::{
GameConnectionToken, GameConnectionTokenPrivate, GamePlayerData, GameServerAddress,
},
players::validate_player_token,
};

#[post("/v1/player/test")]
async fn player_test(app_data: web::Data<AppData>) -> Result<impl Responder, RouteError> {
let player_data = GamePlayerData::generate(Uuid::new_v4(), "SirLynix".into());
#[derive(Deserialize)]
struct GameConnectionParams {
token: String,
}

#[derive(Serialize)]
struct GameConnectionResponse {

Check warning on line 21 in src/game_connection.rs

View workflow job for this annotation

GitHub Actions / clippy

struct `GameConnectionResponse` is never constructed

warning: struct `GameConnectionResponse` is never constructed --> src/game_connection.rs:21:8 | 21 | struct GameConnectionResponse { | ^^^^^^^^^^^^^^^^^^^^^^
uuid: String,
nickname: String,
}

#[post("/v1/game/connect")]
async fn route_game_connect(
app_data: web::Data<AppData>,
pg_pool: web::Data<deadpool_postgres::Pool>,
params: web::Json<GameConnectionParams>,
) -> Result<impl Responder, RouteError> {
let pg_client = pg_pool.get().await?;
let player_id = validate_player_token(&pg_client, &params.token).await?;

let find_player_info = pg_client
.prepare_typed_cached(
"SELECT uuid, nickname FROM players WHERE id = $1",
&[Type::INT4],
)
.await?;

let player_result = pg_client.query(&find_player_info, &[&player_id]).await?;
if player_result.is_empty() {
return Err(RouteError::InvalidRequest(RequestError::new(
ErrorCode::AuthenticationInvalidToken,
"Invalid token".to_string(),
)));
}

let uuid: Uuid = player_result[0].get(0);
let nickname: String = player_result[0].get(1);

let player_data = GamePlayerData::generate(uuid, nickname);

let server_address = GameServerAddress {
address: app_data.config.game_server_address.clone(),
Expand Down
17 changes: 9 additions & 8 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ use actix_governor::{Governor, GovernorConfig, GovernorConfigBuilder};
use actix_web::{middleware, web, App, HttpServer};
use cached::TimedCache;
use confy::ConfyError;
use game_connection::player_test;
use game_connection::route_game_connect;
use players::route_player_auth;
use std::sync::Mutex;

use crate::app_data::AppData;
use crate::config::ApiConfig;
use crate::fetcher::Fetcher;
use crate::players::{player_authenticate, player_create};
use crate::version::game_version;
use crate::players::route_player_create;
use crate::version::route_game_version;

mod app_data;
mod config;
Expand Down Expand Up @@ -62,7 +63,7 @@ async fn main() -> Result<(), std::io::Error> {
let test_client = pg_pool.get().await.expect("failed to connect to database");
drop(test_client);

std::env::set_var("RUST_LOG", "debug,actix_web=debug");
std::env::set_var("RUST_LOG", "info,actix_web=info");
env_logger::init();

let bind_address = format!("{}:{}", config.listen_address, config.listen_port);
Expand All @@ -87,13 +88,13 @@ async fn main() -> Result<(), std::io::Error> {
.wrap(Governor::new(&governor_conf))
.app_data(data_config.clone())
.app_data(pg_pool.clone())
.service(game_version)
.service(player_authenticate)
.service(player_test)
.service(route_game_version)
.service(route_player_auth)
.service(route_game_connect)
.service(
web::scope("")
.wrap(Governor::new(&player_create_governor_conf))
.service(player_create),
.service(route_player_create),
)
})
.bind(bind_address)?
Expand Down
78 changes: 55 additions & 23 deletions src/players.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,35 @@ use uuid::Uuid;
use crate::app_data::AppData;
use crate::errors::api::{ErrorCode, RequestError, RouteError};

pub async fn validate_player_token(
pg_client: &deadpool_postgres::Client,
token: &str,
) -> Result<i32, RouteError> {
if token.is_empty() || token.len() > 64 {
return Err(RouteError::InvalidRequest(RequestError::new(
ErrorCode::AuthenticationInvalidToken,
"Invalid token".to_string(),
)));
}

let find_token_statement = pg_client
.prepare_typed_cached(
"SELECT player_id FROM player_tokens WHERE token = $1",
&[Type::VARCHAR],
)
.await?;

let token_result = pg_client.query(&find_token_statement, &[&token]).await?;
if token_result.is_empty() {
return Err(RouteError::InvalidRequest(RequestError::new(
ErrorCode::AuthenticationInvalidToken,
"Invalid token".to_string(),
)));
}

Ok(token_result[0].get(0))
}

#[derive(Deserialize)]
struct CreatePlayerParams {
nickname: String,
Expand All @@ -22,7 +51,7 @@ struct CreatePlayerResponse {
}

#[post("/v1/players")]
async fn player_create(
async fn route_player_create(
app_data: web::Data<AppData>,
pg_pool: web::Data<deadpool_postgres::Pool>,
params: web::Json<CreatePlayerParams>,
Expand Down Expand Up @@ -110,28 +139,13 @@ struct AuthenticationResponse {
nickname: String,
}

#[post("/v1/player/authenticate")]
async fn player_authenticate(
#[post("/v1/player/auth")]
async fn route_player_auth(
pg_pool: web::Data<deadpool_postgres::Pool>,
params: web::Json<AuthenticationParams>,
) -> Result<impl Responder, RouteError> {
let token = &params.token;

if token.is_empty() || token.len() > 64 {
return Err(RouteError::InvalidRequest(RequestError::new(
ErrorCode::AuthenticationInvalidToken,
"Invalid token".to_string(),
)));
}

let pg_client = pg_pool.get().await?;

let find_token_statement = pg_client
.prepare_typed_cached(
"SELECT player_id FROM player_tokens WHERE token = $1",
&[Type::VARCHAR],
)
.await?;
let player_id = validate_player_token(&pg_client, &params.token).await?;

let find_player_info = pg_client
.prepare_typed_cached(
Expand All @@ -140,16 +154,34 @@ async fn player_authenticate(
)
.await?;

let token_result = pg_client.query(&find_token_statement, &[&token]).await?;
if token_result.is_empty() {
let player_result = pg_client.query(&find_player_info, &[&player_id]).await?;
if player_result.is_empty() {
return Err(RouteError::InvalidRequest(RequestError::new(
ErrorCode::AuthenticationInvalidToken,
"Invalid token".to_string(),
)));
}

let player_id: i32 = token_result[0].get(0);
let player_result = pg_client.query(&find_player_info, &[&player_id]).await?;
// Update last connection time in a separate task as its result won't affect the route
tokio::spawn(async move {
match pg_client
.prepare_typed_cached(
"UPDATE players SET last_connection_time = NOW() WHERE id = $1",
&[Type::INT4],
)
.await
{
Ok(statement) => {
let res = pg_client.query(&statement, &[&player_id]).await;
if let Err(err) = res {
eprintln!("failed to update player {player_id} connection time: {err}");
}
}
Err(err) => {
eprintln!("failed to update player {player_id} connection time (failed to prepare query): {err}");
}
}
});

let uuid: Uuid = player_result[0].get(0);
let nickname: String = player_result[0].get(1);
Expand Down
2 changes: 1 addition & 1 deletion src/version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub(crate) enum CachedReleased {
}

#[get("/game_version")]
async fn game_version(
async fn route_game_version(
app_data: web::Data<AppData>,
ver_query: web::Query<VersionQuery>,
) -> impl Responder {
Expand Down

0 comments on commit cc1219d

Please sign in to comment.