Skip to content

Commit

Permalink
Merge pull request #13 from amiller68/board-stream
Browse files Browse the repository at this point in the history
streaming bones
  • Loading branch information
amiller68 authored Jan 26, 2024
2 parents 78e8b94 + a3c41ea commit 1d6d367
Show file tree
Hide file tree
Showing 22 changed files with 344 additions and 215 deletions.
14 changes: 8 additions & 6 deletions src/api/games/create_game.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,23 @@ use axum::{
response::{IntoResponse, Response},
};

use crate::api::models::ApiGame;
use crate::api::models::ApiGameItem;
use crate::database::models::NewGame;
use crate::AppState;

pub async fn handler(State(state): State<AppState>) -> Result<impl IntoResponse, CreateGameError> {
let game = NewGame::create(&state.database()).await?;

let api_game = ApiGame::from(game);
Ok(NewGameTemplate { api_game })
let api_game = ApiGameItem::from(game);
Ok(GameItemTemplate {
game_item: api_game,
})
}

#[derive(Template)]
#[template(path = "game.html")]
struct NewGameTemplate {
api_game: ApiGame,
#[template(path = "game_item.html")]
struct GameItemTemplate {
game_item: ApiGameItem,
}

#[derive(Debug, thiserror::Error)]
Expand Down
47 changes: 21 additions & 26 deletions src/api/games/make_move.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
use askama::Template;
use axum::{
extract::{Path, State},
http::StatusCode,
response::{IntoResponse, Response},
Form,
Extension, Form,
};
use sqlx::types::Uuid;

use crate::api::models::ApiGameBoard;
use crate::database::models::{Game, GameBoard, GameError};
use crate::AppState;

#[derive(serde::Deserialize)]
use super::watch_game_sse::{GameUpdate, GameUpdateStream};

#[derive(serde::Deserialize, Debug)]
pub struct MakeMoveRequest {
#[serde(rename = "uciMove")]
uci_move: String,
Expand All @@ -19,6 +20,7 @@ pub struct MakeMoveRequest {

pub async fn handler(
State(state): State<AppState>,
Extension(tx): Extension<GameUpdateStream>,
Path(game_id): Path<Uuid>,
Form(request): Form<MakeMoveRequest>,
) -> Result<impl IntoResponse, ReadBoardError> {
Expand All @@ -30,32 +32,15 @@ pub async fn handler(
}

// Returns the updated board if the move was valid. Otherwise, returns the latest board.
let game_board = GameBoard::make_move(&mut conn, game_id, &uci_move, resign).await?;
GameBoard::make_move(&mut conn, game_id, &uci_move, resign).await?;

// If we got here, then either we made a valid move
// or no changes were made to the database (invalid move)
conn.commit().await?;

let board = game_board.board().clone();
let status = game_board.status().clone();
let winner = game_board.winner().clone();
let outcome = game_board.outcome().clone();
let game_id = game_id.to_string();
let api_board = ApiGameBoard {
game_id,
board,
status,
winner,
outcome,
};

Ok(TemplateApiGameBoard { api_board })
}
if tx.send(GameUpdate).is_err() {
tracing::warn!("failed to send game update: game_id={}", game_id);
}

#[derive(Template)]
#[template(path = "board.html")]
struct TemplateApiGameBoard {
api_board: ApiGameBoard,
Ok(StatusCode::OK)
}

#[derive(Debug, thiserror::Error)]
Expand All @@ -75,6 +60,16 @@ impl IntoResponse for ReadBoardError {
let body = format!("{}", self);
(axum::http::StatusCode::NOT_FOUND, body).into_response()
}
ReadBoardError::Game(e) => match e {
GameError::InvalidMove(_) | GameError::GameComplete => {
let body = format!("{}", e);
(axum::http::StatusCode::BAD_REQUEST, body).into_response()
}
_ => {
let body = format!("internal server error: {}", e);
(axum::http::StatusCode::INTERNAL_SERVER_ERROR, body).into_response()
}
},
_ => {
let body = format!("{}", self);
(axum::http::StatusCode::INTERNAL_SERVER_ERROR, body).into_response()
Expand Down
2 changes: 2 additions & 0 deletions src/api/games/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ pub mod create_game;
pub mod make_move;
pub mod read_all_games;
pub mod read_game;
pub mod read_game_board;
pub mod watch_game_sse;
12 changes: 6 additions & 6 deletions src/api/games/read_all_games.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use axum::{
response::{IntoResponse, Response},
};

use crate::api::models::ApiGame;
use crate::api::models::ApiGameItem;
use crate::database::models::{Game, GameError};
use crate::AppState;

Expand All @@ -13,14 +13,14 @@ pub async fn handler(
) -> Result<impl IntoResponse, ReadAllGamesError> {
let mut conn = state.database().acquire().await?;
let games = Game::read_all(&mut conn).await?;
let api_games = games.into_iter().map(ApiGame::from).collect();
Ok(Records { api_games })
let game_items = games.into_iter().map(ApiGameItem::from).collect();
Ok(GameList { game_items })
}

#[derive(Template)]
#[template(path = "games.html")]
struct Records {
api_games: Vec<ApiGame>,
#[template(path = "game_list.html")]
struct GameList {
game_items: Vec<ApiGameItem>,
}

#[derive(Debug, thiserror::Error)]
Expand Down
19 changes: 7 additions & 12 deletions src/api/games/read_game.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,18 @@ pub async fn handler(
if !Game::exists(&mut conn, game_id).await? {
return Err(ReadBoardError::NotFound);
}

let game_board = GameBoard::latest(&mut conn, game_id).await?;
let board = game_board.board().clone();
let api_board = ApiGameBoard {
board,
status: game_board.status().clone(),
winner: game_board.winner().clone(),
outcome: game_board.outcome().clone(),
game_id: game_id.to_string(),
};

Ok(TemplateApiGameBoard { api_board })

let api_game_board = ApiGameBoard::from(game_board);

Ok(TemplateApiGameBoard { api_game_board })
}

#[derive(Template)]
#[template(path = "board.html")]
#[template(path = "game_index.html")]
struct TemplateApiGameBoard {
api_board: ApiGameBoard,
api_game_board: ApiGameBoard,
}

#[derive(Debug, thiserror::Error)]
Expand Down
59 changes: 59 additions & 0 deletions src/api/games/read_game_board.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use askama::Template;
use axum::{
extract::{Path, State},
response::{IntoResponse, Response},
};
use sqlx::types::Uuid;

use crate::api::models::ApiGameBoard;
use crate::database::models::{Game, GameBoard, GameError};
use crate::AppState;

pub async fn handler(
State(state): State<AppState>,
Path(game_id): Path<Uuid>,
) -> Result<impl IntoResponse, ReadBoardError> {
let mut conn = state.database().acquire().await?;
if !Game::exists(&mut conn, game_id).await? {
return Err(ReadBoardError::NotFound);
}

let game_board = GameBoard::latest(&mut conn, game_id).await?;

tracing::info!("read game board: {:?}", game_board);

let api_game_board = ApiGameBoard::from(game_board);

Ok(TemplateApiGameBoard { api_game_board })
}

#[derive(Template)]
#[template(path = "game_board.html")]
struct TemplateApiGameBoard {
api_game_board: ApiGameBoard,
}

#[derive(Debug, thiserror::Error)]
pub enum ReadBoardError {
#[error("sqlx error: {0}")]
Sqlx(#[from] sqlx::Error),
#[error("game error: {0}")]
Game(#[from] GameError),
#[error("game not found")]
NotFound,
}

impl IntoResponse for ReadBoardError {
fn into_response(self) -> Response {
match self {
ReadBoardError::NotFound => {
let body = format!("{}", self);
(axum::http::StatusCode::NOT_FOUND, body).into_response()
}
_ => {
let body = format!("{}", self);
(axum::http::StatusCode::INTERNAL_SERVER_ERROR, body).into_response()
}
}
}
}
39 changes: 39 additions & 0 deletions src/api/games/watch_game_sse.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
use std::convert::Infallible;
use std::time::Duration;

use axum::{
extract::Path,
response::{sse::Event, Sse},
Extension,
};
use serde::Serialize;
use sqlx::types::Uuid;
use tokio::sync::broadcast::Sender;
use tokio_stream::wrappers::BroadcastStream;
use tokio_stream::{Stream, StreamExt as _};

pub type GameUpdateStream = Sender<GameUpdate>;

#[derive(Clone, Serialize, Debug)]
pub struct GameUpdate;

pub async fn handler(
Path(game_id): Path<Uuid>,
Extension(tx): Extension<GameUpdateStream>,
) -> Sse<impl Stream<Item = Result<Event, Infallible>>> {
let rx = tx.subscribe();

let stream = BroadcastStream::new(rx);

// Catch all updata events for this game
Sse::new(
stream
.map(move |_| Event::default().event(format!("game-update-{}", game_id)))
.map(Ok),
)
.keep_alive(
axum::response::sse::KeepAlive::new()
.interval(Duration::from_secs(60))
.text("keep-alive-text"),
)
}
2 changes: 1 addition & 1 deletion src/api/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
pub mod games;
mod models;
pub mod models;
13 changes: 13 additions & 0 deletions src/api/models/api_board.rs → src/api/models/api_game_board.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use pleco::core::sq::SQ as Sq;
use pleco::core::Piece;
use pleco::core::Player;

use crate::database::models::GameBoard;
use crate::database::models::GameOutcome;
use crate::database::models::GameStatus;
use crate::database::models::GameWinner;
Expand All @@ -15,6 +16,18 @@ pub struct ApiGameBoard {
pub outcome: Option<GameOutcome>,
}

impl From<GameBoard> for ApiGameBoard {
fn from(game_board: GameBoard) -> Self {
Self {
game_id: game_board.id().to_string(),
board: game_board.board().clone(),
status: game_board.status().clone(),
winner: game_board.winner().clone(),
outcome: game_board.outcome().clone(),
}
}
}

impl ApiGameBoard {
pub fn game_id(&self) -> &str {
&self.game_id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ use crate::database::models::GameOutcome;
use crate::database::models::GameStatus;
use crate::database::models::GameWinner;

pub struct ApiGame {
pub struct ApiGameItem {
id: String,
status: GameStatus,
winner: Option<GameWinner>,
outcome: Option<GameOutcome>,
}

impl ApiGame {
impl ApiGameItem {
pub fn id(&self) -> &str {
&self.id
}
Expand All @@ -34,7 +34,7 @@ impl ApiGame {
}
}

impl From<Game> for ApiGame {
impl From<Game> for ApiGameItem {
fn from(game: Game) -> Self {
Self {
id: game.id().to_string(),
Expand Down
8 changes: 4 additions & 4 deletions src/api/models/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
mod api_board;
mod api_game;
mod api_game_board;
mod api_game_item;

pub use api_board::ApiGameBoard;
pub use api_game::ApiGame;
pub use api_game_board::ApiGameBoard;
pub use api_game_item::ApiGameItem;
Loading

0 comments on commit 1d6d367

Please sign in to comment.