Initial Commit
This commit is contained in:
commit
a7a2d5e9c5
10 changed files with 1636 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
target
|
1426
Cargo.lock
generated
Normal file
1426
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
2
Cargo.toml
Normal file
2
Cargo.toml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
[workspace]
|
||||||
|
members = ["lobby"]
|
18
lobby/Cargo.toml
Normal file
18
lobby/Cargo.toml
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
[package]
|
||||||
|
name = "mad-lobby-service"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
axum = { version = "0.8" }
|
||||||
|
juniper = "0.16"
|
||||||
|
tokio = { version = "1.20", features = ["macros", "net", "rt-multi-thread", "time"] }
|
||||||
|
tokio-stream = "0.1"
|
||||||
|
tokio-tungstenite = "0.26"
|
||||||
|
anyhow = "1.0"
|
||||||
|
juniper_axum = "0.2"
|
||||||
|
rand = "0.9"
|
||||||
|
jwt = "0.16"
|
||||||
|
hmac = "0.12"
|
||||||
|
sha2 = "0.10"
|
||||||
|
uuid = { version = "1.16", features = ["v4"] }
|
0
lobby/LICENSE
Normal file
0
lobby/LICENSE
Normal file
0
lobby/src/client/mod.rs
Normal file
0
lobby/src/client/mod.rs
Normal file
61
lobby/src/main.rs
Normal file
61
lobby/src/main.rs
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
pub mod client;
|
||||||
|
pub mod server;
|
||||||
|
|
||||||
|
use std::{net::SocketAddr, sync::Arc};
|
||||||
|
|
||||||
|
use axum::{
|
||||||
|
Extension, Router,
|
||||||
|
response::Html,
|
||||||
|
routing::{MethodFilter, get, on},
|
||||||
|
};
|
||||||
|
use juniper::{graphql_object, EmptyMutation, EmptySubscription, RootNode};
|
||||||
|
use juniper_axum::{graphiql, graphql, playground};
|
||||||
|
use tokio::net::TcpListener;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct Query;
|
||||||
|
|
||||||
|
#[graphql_object]
|
||||||
|
impl Query {
|
||||||
|
/// Adds two `a` and `b` numbers.
|
||||||
|
fn add(a: i32, b: i32) -> i32 {
|
||||||
|
a + b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Schema = RootNode<'static, Query, EmptyMutation, EmptySubscription>;
|
||||||
|
|
||||||
|
async fn homepage() -> Html<&'static str> {
|
||||||
|
"<html><h1>juniper_axum/simple example</h1>\
|
||||||
|
<div>visit <a href=\"/graphiql\">GraphiQL</a></div>\
|
||||||
|
<div>visit <a href=\"/playground\">GraphQL Playground</a></div>\
|
||||||
|
</html>"
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
|
||||||
|
let schema = Schema::new(Query, EmptyMutation::new(), EmptySubscription::new());
|
||||||
|
|
||||||
|
let app = Router::new()
|
||||||
|
.route(
|
||||||
|
"/graphql",
|
||||||
|
on(
|
||||||
|
MethodFilter::GET.or(MethodFilter::POST),
|
||||||
|
graphql::<Arc<Schema>>,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.route("/graphiql", get(graphiql("/graphql", "/subscriptions")))
|
||||||
|
.route("/playground", get(playground("/graphql", "/subscriptions")))
|
||||||
|
.route("/", get(homepage))
|
||||||
|
.layer(Extension(Arc::new(schema)));
|
||||||
|
|
||||||
|
let addr = SocketAddr::from(([127, 0, 0, 1], 8080));
|
||||||
|
let listener = TcpListener::bind(addr)
|
||||||
|
.await
|
||||||
|
.unwrap_or_else(|e| panic!("failed to listen on {addr}: {e}"));
|
||||||
|
axum::serve(listener, app)
|
||||||
|
.await
|
||||||
|
.unwrap_or_else(|e| panic!("failed to run `axum::serve`: {e}"));
|
||||||
|
}
|
31
lobby/src/server/gql_schema.rs
Normal file
31
lobby/src/server/gql_schema.rs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
use juniper::graphql_object;
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* Roots */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct Query;
|
||||||
|
|
||||||
|
#[graphql_object]
|
||||||
|
impl Query {
|
||||||
|
fn lobbies(&self) -> String {
|
||||||
|
"".to_string() // TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* Objects */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct QueryLobby {
|
||||||
|
id: String
|
||||||
|
}
|
||||||
|
|
||||||
|
#[graphql_object]
|
||||||
|
impl QueryLobby {
|
||||||
|
fn id(&self) -> String {
|
||||||
|
self.id.clone()
|
||||||
|
}
|
||||||
|
}
|
95
lobby/src/server/logic.rs
Normal file
95
lobby/src/server/logic.rs
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
use std::{
|
||||||
|
collections::BTreeMap,
|
||||||
|
time::{SystemTime, UNIX_EPOCH},
|
||||||
|
};
|
||||||
|
|
||||||
|
use hmac::{Hmac, Mac};
|
||||||
|
use rand::RngCore;
|
||||||
|
use sha2::Sha256;
|
||||||
|
|
||||||
|
fn unix_time() -> u64 {
|
||||||
|
SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.unwrap()
|
||||||
|
.as_secs()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum LobbyStatus {
|
||||||
|
Alive,
|
||||||
|
Inactive,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Lobby {
|
||||||
|
last_heartbeat: u64,
|
||||||
|
status: LobbyStatus,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Lobby {
|
||||||
|
fn send_heartbeat(&mut self) {
|
||||||
|
self.last_heartbeat = unix_time();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct LobbyManager {
|
||||||
|
heartbeat_timeout: u64,
|
||||||
|
server_key: Hmac<Sha256>,
|
||||||
|
lobbies: BTreeMap<String, Lobby>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LobbyManager {
|
||||||
|
fn new() -> Self {
|
||||||
|
let mut server_key_token: [u8; 64] = [0; 64];
|
||||||
|
{
|
||||||
|
rand::rng().fill_bytes(&mut server_key_token);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Handle this expect better
|
||||||
|
let server_key: Hmac<Sha256> = Hmac::new_from_slice(&server_key_token).expect("bad token?");
|
||||||
|
|
||||||
|
Self {
|
||||||
|
heartbeat_timeout: 30,
|
||||||
|
server_key,
|
||||||
|
lobbies: BTreeMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_lobby(&mut self) {
|
||||||
|
let lobby = Lobby {
|
||||||
|
last_heartbeat: unix_time(),
|
||||||
|
status: LobbyStatus::Alive,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.lobbies.insert(uuid::Uuid::new_v4().to_string(), lobby);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tick(&mut self) {
|
||||||
|
for lobby in &mut self.lobbies {
|
||||||
|
if lobby.1.last_heartbeat > unix_time() + self.heartbeat_timeout {
|
||||||
|
match lobby.1.status {
|
||||||
|
LobbyStatus::Alive => lobby.1.status = LobbyStatus::Inactive,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cull_lobbies(&mut self) -> Result<usize, ()> {
|
||||||
|
let mut key_list: Vec<String> = Vec::new();
|
||||||
|
|
||||||
|
{
|
||||||
|
for lobby in &self.lobbies {
|
||||||
|
match lobby.1.status {
|
||||||
|
LobbyStatus::Inactive => key_list.push(lobby.0.clone()),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
for key in &key_list {
|
||||||
|
self.lobbies.remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(key_list.len())
|
||||||
|
}
|
||||||
|
}
|
2
lobby/src/server/mod.rs
Normal file
2
lobby/src/server/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
mod gql_schema;
|
||||||
|
mod logic;
|
Loading…
Add table
Reference in a new issue