From 9a09ff717ea10a147c0d85d8865c5e00a99a17ac Mon Sep 17 00:00:00 2001 From: Nikita Vilunov Date: Wed, 1 May 2024 17:30:31 +0200 Subject: [PATCH] management api endpoints for rooms --- Cargo.lock | 1 + Cargo.toml | 1 + crates/lavina-core/src/player.rs | 4 +- crates/mgmt-api/src/lib.rs | 2 + crates/mgmt-api/src/rooms.rs | 24 +++++++++++ src/http.rs | 72 ++++++++++++++++++++++++++++---- 6 files changed, 94 insertions(+), 10 deletions(-) create mode 100644 crates/mgmt-api/src/rooms.rs diff --git a/Cargo.lock b/Cargo.lock index e2d52e1..f3bbd73 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1062,6 +1062,7 @@ version = "0.0.3-dev" dependencies = [ "anyhow", "assert_matches", + "chrono", "clap", "derive_more", "figment", diff --git a/Cargo.toml b/Cargo.toml index 093e62c..8e026da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,6 +64,7 @@ opentelemetry-semantic-conventions = "0.14.0" opentelemetry_sdk = { version = "0.22.1", features = ["rt-tokio"] } opentelemetry-otlp = "0.15.0" tracing-opentelemetry = "0.23.0" +chrono.workspace = true [dev-dependencies] assert_matches.workspace = true diff --git a/crates/lavina-core/src/player.rs b/crates/lavina-core/src/player.rs index 30635b0..08ce37a 100644 --- a/crates/lavina-core/src/player.rs +++ b/crates/lavina-core/src/player.rs @@ -297,7 +297,7 @@ impl PlayerRegistry { } #[tracing::instrument(skip(self), name = "PlayerRegistry::get_or_launch_player")] - pub async fn get_or_launch_player(&mut self, id: &PlayerId) -> PlayerHandle { + pub async fn get_or_launch_player(&self, id: &PlayerId) -> PlayerHandle { let inner = self.0.read().await; if let Some((handle, _)) = inner.players.get(id) { handle.clone() @@ -322,7 +322,7 @@ impl PlayerRegistry { } #[tracing::instrument(skip(self), name = "PlayerRegistry::connect_to_player")] - pub async fn connect_to_player(&mut self, id: &PlayerId) -> PlayerConnection { + pub async fn connect_to_player(&self, id: &PlayerId) -> PlayerConnection { let player_handle = self.get_or_launch_player(id).await; player_handle.subscribe().await } diff --git a/crates/mgmt-api/src/lib.rs b/crates/mgmt-api/src/lib.rs index c21ff85..0ffbfdb 100644 --- a/crates/mgmt-api/src/lib.rs +++ b/crates/mgmt-api/src/lib.rs @@ -1,5 +1,7 @@ use serde::{Deserialize, Serialize}; +pub mod rooms; + #[derive(Serialize, Deserialize)] pub struct ErrorResponse<'a> { pub code: &'a str, diff --git a/crates/mgmt-api/src/rooms.rs b/crates/mgmt-api/src/rooms.rs new file mode 100644 index 0000000..c091467 --- /dev/null +++ b/crates/mgmt-api/src/rooms.rs @@ -0,0 +1,24 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize)] +pub struct SendMessageReq<'a> { + pub room_id: &'a str, + pub author_id: &'a str, + pub message: &'a str, +} + +#[derive(Serialize, Deserialize)] +pub struct SetTopicReq<'a> { + pub room_id: &'a str, + pub author_id: &'a str, + pub topic: &'a str, +} + +pub mod paths { + pub const SEND_MESSAGE: &'static str = "/mgmt/rooms/send_message"; + pub const SET_TOPIC: &'static str = "/mgmt/rooms/set_topic"; +} + +pub mod errors { + pub const ROOM_NOT_FOUND: &'static str = "room_not_found"; +} diff --git a/src/http.rs b/src/http.rs index ae64676..f13ee7a 100644 --- a/src/http.rs +++ b/src/http.rs @@ -13,10 +13,10 @@ use serde::{Deserialize, Serialize}; use tokio::net::TcpListener; use lavina_core::auth::{Authenticator, UpdatePasswordResult}; -use lavina_core::player::{PlayerId, PlayerRegistry}; +use lavina_core::player::{PlayerId, PlayerRegistry, SendMessageResult}; use lavina_core::prelude::*; use lavina_core::repo::Storage; -use lavina_core::room::RoomRegistry; +use lavina_core::room::{RoomId, RoomRegistry}; use lavina_core::terminator::Terminator; use lavina_core::LavinaCore; @@ -88,6 +88,8 @@ async fn route( (&Method::POST, paths::CREATE_PLAYER) => endpoint_create_player(request, storage).await.or5xx(), (&Method::POST, paths::STOP_PLAYER) => endpoint_stop_player(request, core.players).await.or5xx(), (&Method::POST, paths::SET_PASSWORD) => endpoint_set_password(request, storage).await.or5xx(), + (&Method::POST, rooms::paths::SEND_MESSAGE) => endpoint_send_room_message(request, core).await.or5xx(), + (&Method::POST, rooms::paths::SET_TOPIC) => endpoint_set_room_topic(request, core).await.or5xx(), _ => endpoint_not_found(), }; Ok(res) @@ -139,9 +141,7 @@ async fn endpoint_stop_player( let Some(()) = players.stop_player(&player_id).await? else { return Ok(player_not_found()); }; - let mut response = Response::new(Full::::default()); - *response.status_mut() = StatusCode::NO_CONTENT; - Ok(response) + Ok(empty_204_request()) } #[tracing::instrument(skip_all)] @@ -160,9 +160,48 @@ async fn endpoint_set_password( return Ok(player_not_found()); } } - let mut response = Response::new(Full::::default()); - *response.status_mut() = StatusCode::NO_CONTENT; - Ok(response) + Ok(empty_204_request()) +} + +async fn endpoint_send_room_message( + request: Request, + mut core: LavinaCore, +) -> Result>> { + let str = request.collect().await?.to_bytes(); + let Ok(req) = serde_json::from_slice::(&str[..]) else { + return Ok(malformed_request()); + }; + let Ok(room_id) = RoomId::from(req.room_id) else { + return Ok(room_not_found()); + }; + let Ok(player_id) = PlayerId::from(req.author_id) else { + return Ok(player_not_found()); + }; + let mut player = core.players.connect_to_player(&player_id).await; + let res = player.send_message(room_id, req.message.into()).await?; + match res { + SendMessageResult::NoSuchRoom => Ok(room_not_found()), + SendMessageResult::Success(_) => Ok(empty_204_request()), + } +} + +async fn endpoint_set_room_topic( + request: Request, + core: LavinaCore, +) -> Result>> { + let str = request.collect().await?.to_bytes(); + let Ok(req) = serde_json::from_slice::(&str[..]) else { + return Ok(malformed_request()); + }; + let Ok(room_id) = RoomId::from(req.room_id) else { + return Ok(room_not_found()); + }; + let Ok(player_id) = PlayerId::from(req.author_id) else { + return Ok(player_not_found()); + }; + let mut player = core.players.connect_to_player(&player_id).await; + player.change_topic(room_id, req.topic.into()).await?; + Ok(empty_204_request()) } fn endpoint_not_found() -> Response> { @@ -188,6 +227,17 @@ fn player_not_found() -> Response> { response } +fn room_not_found() -> Response> { + let payload = ErrorResponse { + code: rooms::errors::ROOM_NOT_FOUND, + message: "No such room exists", + } + .to_body(); + let mut response = Response::new(payload); + *response.status_mut() = StatusCode::UNPROCESSABLE_ENTITY; + response +} + fn malformed_request() -> Response> { let payload = ErrorResponse { code: errors::MALFORMED_REQUEST, @@ -200,6 +250,12 @@ fn malformed_request() -> Response> { return response; } +fn empty_204_request() -> Response> { + let mut response = Response::new(Full::::default()); + *response.status_mut() = StatusCode::NO_CONTENT; + response +} + trait Or5xx { fn or5xx(self) -> Response>; }