summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKjetil Orbekk <kj@orbekk.com>2022-11-15 12:25:25 -0500
committerKjetil Orbekk <kj@orbekk.com>2022-11-15 12:25:25 -0500
commit6296e97fafd4bb5063541bee83061c398f31d19e (patch)
treeacf9622d16df7d53cd42b4df0bd88b9721f78f8e
parent4c0109a8c40012f75e3d0d900c0ef41893cfb4bb (diff)
Add journaling trait for game objects
-rw-r--r--protocol/src/lib.rs1
-rw-r--r--server/migrations/20221008120534_init.down.sql2
-rw-r--r--server/migrations/20221008120534_init.up.sql7
-rw-r--r--server/src/error.rs3
-rw-r--r--server/src/main.rs9
-rw-r--r--server/src/play.rs56
6 files changed, 74 insertions, 4 deletions
diff --git a/protocol/src/lib.rs b/protocol/src/lib.rs
index 4294e17..b10e7e1 100644
--- a/protocol/src/lib.rs
+++ b/protocol/src/lib.rs
@@ -14,4 +14,5 @@ pub struct Table {
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
pub struct TableView {
+ pub m: String,
}
diff --git a/server/migrations/20221008120534_init.down.sql b/server/migrations/20221008120534_init.down.sql
index 385537a..0c9bbed 100644
--- a/server/migrations/20221008120534_init.down.sql
+++ b/server/migrations/20221008120534_init.down.sql
@@ -2,6 +2,8 @@
begin;
drop table if exists sessions;
drop table if exists table_players;
+drop table if exists table_journal;
drop table if exists active_tables;
drop table if exists players;
+drop type if exists player_position;
commit;
diff --git a/server/migrations/20221008120534_init.up.sql b/server/migrations/20221008120534_init.up.sql
index 8c53bb8..e4b9eb1 100644
--- a/server/migrations/20221008120534_init.up.sql
+++ b/server/migrations/20221008120534_init.up.sql
@@ -16,6 +16,13 @@ create table active_tables (
id uuid primary key not null
);
+create table table_journal (
+ table_id uuid references active_tables(id) not null,
+ seq bigint not null,
+ payload jsonb
+);
+create unique index journal_entry on table_journal (table_id, seq);
+
create type player_position as enum ('west', 'north', 'east', 'south');
create table table_players (
diff --git a/server/src/error.rs b/server/src/error.rs
index 9d82a54..cbd00c3 100644
--- a/server/src/error.rs
+++ b/server/src/error.rs
@@ -46,6 +46,9 @@ pub enum BridgeError {
#[error("Duration out of range")]
DurationOutOfRange(#[from] time::OutOfRangeError),
+
+ #[error("Sequence number mismatch: {0} ≠ {1}")]
+ UpdateConflict(i64, i64),
}
impl BridgeError {
diff --git a/server/src/main.rs b/server/src/main.rs
index 9761afe..9b5adb7 100644
--- a/server/src/main.rs
+++ b/server/src/main.rs
@@ -17,6 +17,7 @@ use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
mod auth;
mod error;
mod server;
+mod play;
use crate::error::BridgeError;
use crate::{
auth::{Authenticator, SessionId},
@@ -61,7 +62,7 @@ async fn main() {
.route("/api/user/info", get(user_info))
.route("/api/table", post(create_table))
.route("/api/table", delete(leave_table))
- .route("/api/table/:id", get(get_table_state))
+ .route("/api/table/:id", get(get_table_view))
// .route("/api/user/table", get(user_table))
.route("/api/login", get(login))
.route(auth::LOGIN_CALLBACK, get(login_callback))
@@ -75,13 +76,13 @@ async fn main() {
.unwrap();
}
-async fn get_table_state(
+async fn get_table_view(
session: AuthenticatedSession,
extension: ContextExtension,
Path(id): Path<Uuid>
-) -> Result<(), BridgeError> {
+) -> Result<Json<protocol::TableView>, BridgeError> {
info!("Getting table state for table {id:}");
- Ok(())
+ Ok(Json(protocol::TableView { m: format!("hello") }))
}
async fn leave_table(
diff --git a/server/src/play.rs b/server/src/play.rs
new file mode 100644
index 0000000..8fe1872
--- /dev/null
+++ b/server/src/play.rs
@@ -0,0 +1,56 @@
+use async_trait::async_trait;
+
+use crate::error::BridgeError;
+
+#[async_trait]
+pub trait Journal {
+ // Append payload to the journal at sequence number `seq`.
+ async fn append(&mut self, seq: i64, payload: serde_json::Value) -> Result<(), BridgeError>;
+
+ // Fetch all journal entries with sequence number greater or equal to `seq`.
+ async fn replay(&mut self, seq: i64) -> Result<Vec<serde_json::Value>, BridgeError>;
+}
+
+pub struct Table {}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[derive(Default)]
+ pub struct TestJournal {
+ log: Vec<Option<serde_json::Value>>,
+ }
+
+ #[async_trait]
+ impl Journal for TestJournal {
+ async fn append(
+ &mut self,
+ seq: i64,
+ payload: serde_json::Value,
+ ) -> Result<(), BridgeError> {
+ if seq != self.log.len() as i64 {
+ return Err(BridgeError::UpdateConflict(self.log.len() as i64, seq));
+ }
+ self.log.push(Some(payload));
+ Ok(())
+ }
+
+ async fn replay(&mut self, seq: i64) -> Result<Vec<serde_json::Value>, BridgeError> {
+ Ok(self.log[seq as usize..]
+ .into_iter()
+ .filter_map(|e| e.clone())
+ .collect())
+ }
+ }
+
+ #[tokio::test]
+ async fn test_journal() {
+ use serde_json::json;
+ let mut jnl: TestJournal = Default::default();
+ assert_eq!(jnl.append(0, json!(10)).await.unwrap(), ());
+ assert!(jnl.append(0, json!(0)).await.is_err());
+ assert_eq!(jnl.append(1, json!(20)).await.unwrap(), ());
+ assert_eq!(jnl.replay(1).await.unwrap(), vec!(json!(20)));
+ }
+}