session handling for players and hosts
This commit is contained in:
parent
fee5576efd
commit
b6a4936247
1
backend/.gitignore
vendored
1
backend/.gitignore
vendored
@ -3,3 +3,4 @@
|
|||||||
/build
|
/build
|
||||||
erl_crash.dump
|
erl_crash.dump
|
||||||
config.toml
|
config.toml
|
||||||
|
/store
|
||||||
|
@ -7,6 +7,7 @@ import gleam/erlang/process
|
|||||||
import gleam/float
|
import gleam/float
|
||||||
import gleam/http/request.{type Request}
|
import gleam/http/request.{type Request}
|
||||||
import gleam/http/response.{type Response}
|
import gleam/http/response.{type Response}
|
||||||
|
import gleam/int
|
||||||
import gleam/io
|
import gleam/io
|
||||||
import gleam/json
|
import gleam/json
|
||||||
import gleam/list
|
import gleam/list
|
||||||
@ -15,6 +16,10 @@ import gleam/otp/actor
|
|||||||
import gleam/result
|
import gleam/result
|
||||||
import gleam/string
|
import gleam/string
|
||||||
import mist.{type Connection, type ResponseData}
|
import mist.{type Connection, type ResponseData}
|
||||||
|
import player
|
||||||
|
import player_session
|
||||||
|
import session
|
||||||
|
import storail
|
||||||
|
|
||||||
pub fn handle_request(
|
pub fn handle_request(
|
||||||
req: Request(Connection),
|
req: Request(Connection),
|
||||||
@ -122,8 +127,42 @@ fn handle_ws_message(state, conn, message) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn register_user(
|
||||||
|
ctx: web.Context,
|
||||||
|
username: String,
|
||||||
|
gamecode: String,
|
||||||
|
) -> #(String, Int) {
|
||||||
|
let secret = crypto.strong_random_bytes(16)
|
||||||
|
let token =
|
||||||
|
crypto.new_hasher(crypto.Sha512)
|
||||||
|
|> crypto.hash_chunk(secret)
|
||||||
|
|> crypto.hash_chunk(bit_array.from_string(username))
|
||||||
|
|> crypto.hash_chunk(bit_array.from_string(gamecode))
|
||||||
|
|> crypto.digest
|
||||||
|
|
||||||
|
let token_hash =
|
||||||
|
crypto.new_hasher(crypto.Sha512)
|
||||||
|
|> crypto.hash_chunk(token)
|
||||||
|
|> crypto.digest
|
||||||
|
|> bit_array.base64_encode(True)
|
||||||
|
|
||||||
|
let assert <<user_id:int-size(64)>> = crypto.strong_random_bytes(8)
|
||||||
|
|
||||||
|
let key = storail.key(ctx.player_sessions, int.to_base16(user_id))
|
||||||
|
|
||||||
|
let assert Ok(Nil) =
|
||||||
|
storail.write(
|
||||||
|
key,
|
||||||
|
player_session.PlayerSession(id: user_id, token_hash:, username:),
|
||||||
|
)
|
||||||
|
|
||||||
|
let token = bit_array.base64_encode(token, True)
|
||||||
|
|
||||||
|
#(token, user_id)
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_client_msg(
|
fn handle_client_msg(
|
||||||
_ctx: web.Context,
|
ctx: web.Context,
|
||||||
req: web.ClientRequest,
|
req: web.ClientRequest,
|
||||||
) -> web.GameResponse {
|
) -> web.GameResponse {
|
||||||
case req.msg {
|
case req.msg {
|
||||||
@ -131,25 +170,30 @@ fn handle_client_msg(
|
|||||||
io.println("Got buzz in @ " <> float.to_string(time))
|
io.println("Got buzz in @ " <> float.to_string(time))
|
||||||
web.AckBuzzer
|
web.AckBuzzer
|
||||||
}
|
}
|
||||||
web.Register(username, gamecode) -> {
|
web.Register(gamecode, username) -> {
|
||||||
let secret = crypto.strong_random_bytes(16)
|
let #(token, user_id) = register_user(ctx, username, gamecode)
|
||||||
let token =
|
let key = storail.key(ctx.sessions, gamecode)
|
||||||
crypto.new_hasher(crypto.Sha512)
|
let assert Ok(session) = storail.read(key)
|
||||||
|> crypto.hash_chunk(secret)
|
|
||||||
|> crypto.hash_chunk(bit_array.from_string(username))
|
|
||||||
|> crypto.hash_chunk(bit_array.from_string(gamecode))
|
|
||||||
|> crypto.digest
|
|
||||||
|
|
||||||
let token_hash =
|
let player = player.Player(name: username, id: user_id, score: 0)
|
||||||
crypto.new_hasher(crypto.Sha512)
|
let session =
|
||||||
|> crypto.hash_chunk(token)
|
session.Session(
|
||||||
|> crypto.digest
|
..session,
|
||||||
|
players: list.append(session.players, [player]),
|
||||||
|
)
|
||||||
|
|
||||||
io.println("New token: " <> bit_array.base64_encode(token_hash, True))
|
let assert Ok(Nil) = storail.write(key, session)
|
||||||
|
|
||||||
let token = bit_array.base64_encode(token, True)
|
|
||||||
|
|
||||||
web.JoinResponse(username, token)
|
web.JoinResponse(username, token)
|
||||||
}
|
}
|
||||||
|
web.CreateRoom(gamecode, username) -> {
|
||||||
|
let #(token, user_id) = register_user(ctx, username, gamecode)
|
||||||
|
|
||||||
|
let key = storail.key(ctx.sessions, gamecode)
|
||||||
|
let session = session.Session(gamecode, user_id, [])
|
||||||
|
let assert Ok(Nil) = storail.write(key, session)
|
||||||
|
|
||||||
|
web.HostResponse(username, token)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,15 +2,39 @@ import config
|
|||||||
import gleam/dynamic/decode
|
import gleam/dynamic/decode
|
||||||
import gleam/erlang/process
|
import gleam/erlang/process
|
||||||
import gleam/json
|
import gleam/json
|
||||||
|
import player_session
|
||||||
import session
|
import session
|
||||||
import storail
|
import storail
|
||||||
|
|
||||||
pub type ClientMessage {
|
pub type ClientMessage {
|
||||||
BuzzIn(time: Float)
|
BuzzIn(time: Float)
|
||||||
Register(game_code: String, username: String)
|
Register(game_code: String, username: String)
|
||||||
|
CreateRoom(game_code: String, username: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn client_message_decoder() -> decode.Decoder(ClientMessage) {
|
pub fn encode_client_message(client_message: ClientMessage) -> json.Json {
|
||||||
|
case client_message {
|
||||||
|
BuzzIn(..) ->
|
||||||
|
json.object([
|
||||||
|
#("type", json.string("buzz_in")),
|
||||||
|
#("time", json.float(client_message.time)),
|
||||||
|
])
|
||||||
|
Register(..) ->
|
||||||
|
json.object([
|
||||||
|
#("type", json.string("register")),
|
||||||
|
#("game_code", json.string(client_message.game_code)),
|
||||||
|
#("username", json.string(client_message.username)),
|
||||||
|
])
|
||||||
|
CreateRoom(..) ->
|
||||||
|
json.object([
|
||||||
|
#("type", json.string("create_room")),
|
||||||
|
#("game_code", json.string(client_message.game_code)),
|
||||||
|
#("username", json.string(client_message.username)),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn client_message_decoder() -> decode.Decoder(ClientMessage) {
|
||||||
use variant <- decode.field("type", decode.string)
|
use variant <- decode.field("type", decode.string)
|
||||||
case variant {
|
case variant {
|
||||||
"BuzzIn" -> {
|
"BuzzIn" -> {
|
||||||
@ -22,7 +46,12 @@ fn client_message_decoder() -> decode.Decoder(ClientMessage) {
|
|||||||
use username <- decode.field("username", decode.string)
|
use username <- decode.field("username", decode.string)
|
||||||
decode.success(Register(game_code:, username:))
|
decode.success(Register(game_code:, username:))
|
||||||
}
|
}
|
||||||
_ -> decode.failure(BuzzIn(time: 0.0), "ClientMessage")
|
"CreateRoom" -> {
|
||||||
|
use game_code <- decode.field("game_code", decode.string)
|
||||||
|
use username <- decode.field("username", decode.string)
|
||||||
|
decode.success(CreateRoom(game_code:, username:))
|
||||||
|
}
|
||||||
|
_ -> decode.failure(BuzzIn(0.0), "ClientMessage")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,6 +67,7 @@ pub fn client_request_decoder() -> decode.Decoder(ClientRequest) {
|
|||||||
|
|
||||||
pub type GameResponse {
|
pub type GameResponse {
|
||||||
JoinResponse(username: String, token: String)
|
JoinResponse(username: String, token: String)
|
||||||
|
HostResponse(username: String, token: String)
|
||||||
AckBuzzer
|
AckBuzzer
|
||||||
ResetBuzzer
|
ResetBuzzer
|
||||||
UpdatePointTotal(score: Int)
|
UpdatePointTotal(score: Int)
|
||||||
@ -51,6 +81,12 @@ pub fn encode_game_response(game_response: GameResponse) -> json.Json {
|
|||||||
#("username", json.string(game_response.username)),
|
#("username", json.string(game_response.username)),
|
||||||
#("token", json.string(game_response.token)),
|
#("token", json.string(game_response.token)),
|
||||||
])
|
])
|
||||||
|
HostResponse(..) ->
|
||||||
|
json.object([
|
||||||
|
#("type", json.string("HostResponse")),
|
||||||
|
#("username", json.string(game_response.username)),
|
||||||
|
#("token", json.string(game_response.token)),
|
||||||
|
])
|
||||||
AckBuzzer -> json.object([#("type", json.string("AckBuzzer"))])
|
AckBuzzer -> json.object([#("type", json.string("AckBuzzer"))])
|
||||||
ResetBuzzer -> json.object([#("type", json.string("ResetBuzzer"))])
|
ResetBuzzer -> json.object([#("type", json.string("ResetBuzzer"))])
|
||||||
UpdatePointTotal(..) ->
|
UpdatePointTotal(..) ->
|
||||||
@ -65,6 +101,7 @@ pub type Context {
|
|||||||
Context(
|
Context(
|
||||||
config: config.Config,
|
config: config.Config,
|
||||||
sessions: storail.Collection(session.Session),
|
sessions: storail.Collection(session.Session),
|
||||||
|
player_sessions: storail.Collection(player_session.PlayerSession),
|
||||||
selector: process.Selector(ClientRequest),
|
selector: process.Selector(ClientRequest),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import gleam/erlang/process
|
|||||||
import gleam/result
|
import gleam/result
|
||||||
import glint
|
import glint
|
||||||
import mist
|
import mist
|
||||||
|
import player_session
|
||||||
import session
|
import session
|
||||||
import storail
|
import storail
|
||||||
|
|
||||||
@ -30,6 +31,17 @@ pub fn setup_session_collection(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn setup_player_session_collection(
|
||||||
|
config: storail.Config,
|
||||||
|
) -> storail.Collection(player_session.PlayerSession) {
|
||||||
|
storail.Collection(
|
||||||
|
name: "PlayerSessions",
|
||||||
|
to_json: player_session.encode_player_session,
|
||||||
|
decoder: player_session.player_session_decoder(),
|
||||||
|
config:,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn run_server() -> glint.Command(Result(Nil, Error)) {
|
pub fn run_server() -> glint.Command(Result(Nil, Error)) {
|
||||||
use <- glint.command_help("Runs the backend of Play of the Game")
|
use <- glint.command_help("Runs the backend of Play of the Game")
|
||||||
use _, args, _ <- glint.command()
|
use _, args, _ <- glint.command()
|
||||||
@ -52,6 +64,7 @@ pub fn run_server() -> glint.Command(Result(Nil, Error)) {
|
|||||||
web.Context(
|
web.Context(
|
||||||
config: cfg,
|
config: cfg,
|
||||||
sessions: setup_session_collection(storail_cfg),
|
sessions: setup_session_collection(storail_cfg),
|
||||||
|
player_sessions: setup_player_session_collection(storail_cfg),
|
||||||
selector: process.new_selector(),
|
selector: process.new_selector(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
31
frontend/src/host_screen.rs
Normal file
31
frontend/src/host_screen.rs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
use macroquad::{
|
||||||
|
color::{BLACK, WHITE},
|
||||||
|
text::draw_text,
|
||||||
|
window::{clear_background, screen_height, screen_width},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::screen::Screen;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct HostScreen {}
|
||||||
|
|
||||||
|
impl Screen for HostScreen {
|
||||||
|
fn handle_frame(&mut self, _ctx: &mut crate::context::Context) {
|
||||||
|
clear_background(WHITE);
|
||||||
|
|
||||||
|
draw_text(
|
||||||
|
"I'm the host!",
|
||||||
|
screen_width() / 2.0,
|
||||||
|
screen_height() / 2.0,
|
||||||
|
100.0,
|
||||||
|
BLACK,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_messages(
|
||||||
|
&mut self,
|
||||||
|
_ctx: &mut crate::context::Context,
|
||||||
|
) -> Option<crate::StateTransition> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
@ -11,6 +11,7 @@ pub struct LoginScreen {
|
|||||||
new_username: String,
|
new_username: String,
|
||||||
new_room_code: String,
|
new_room_code: String,
|
||||||
sent_join: bool,
|
sent_join: bool,
|
||||||
|
sent_host: bool,
|
||||||
retry_count: usize,
|
retry_count: usize,
|
||||||
error_msg: bool,
|
error_msg: bool,
|
||||||
}
|
}
|
||||||
@ -23,6 +24,8 @@ impl Screen for LoginScreen {
|
|||||||
let window_size = Vec2::new(screen_width() * 0.25, screen_height() * 0.25);
|
let window_size = Vec2::new(screen_width() * 0.25, screen_height() * 0.25);
|
||||||
|
|
||||||
let mut pressed_join = false;
|
let mut pressed_join = false;
|
||||||
|
let mut pressed_host = false;
|
||||||
|
|
||||||
root_ui().window(
|
root_ui().window(
|
||||||
hash!("Window"),
|
hash!("Window"),
|
||||||
Vec2::new(
|
Vec2::new(
|
||||||
@ -47,6 +50,16 @@ impl Screen for LoginScreen {
|
|||||||
pressed_join = true;
|
pressed_join = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
ui.group(hash!("Host Group"), group_size, |ui| {
|
||||||
|
if ui.button(Vec2::new(group_size.x * 0.45, 0.0), "Host Game") {
|
||||||
|
info!(
|
||||||
|
"User pressed host with name={} room_code={}",
|
||||||
|
self.new_username, self.new_room_code
|
||||||
|
);
|
||||||
|
|
||||||
|
pressed_host = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if self.error_msg {
|
if self.error_msg {
|
||||||
ui.group(hash!("Error Group"), group_size, |ui| {
|
ui.group(hash!("Error Group"), group_size, |ui| {
|
||||||
@ -64,16 +77,37 @@ impl Screen for LoginScreen {
|
|||||||
|
|
||||||
self.sent_join = true;
|
self.sent_join = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if pressed_host {
|
||||||
|
ctx.send_msg(&ClientMessage::CreateRoom(Register {
|
||||||
|
game_code: self.new_room_code.clone(),
|
||||||
|
username: self.new_username.clone(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
self.sent_host = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_messages(&mut self, ctx: &mut Context) -> Option<StateTransition> {
|
fn handle_messages(&mut self, ctx: &mut Context) -> Option<StateTransition> {
|
||||||
if self.sent_join {
|
if self.sent_join || self.sent_host {
|
||||||
let msg = ctx.recv_msg();
|
let msg = ctx.recv_msg();
|
||||||
|
|
||||||
if let Some(GameResponse::JoinResponse { username, token }) = msg {
|
if let Some(msg) = msg {
|
||||||
ctx.username = username;
|
match msg {
|
||||||
ctx.token = token;
|
GameResponse::JoinResponse { username, token } => {
|
||||||
return Some(StateTransition::JoinAsPlayer);
|
ctx.username = username;
|
||||||
|
ctx.token = token;
|
||||||
|
return Some(StateTransition::JoinAsPlayer);
|
||||||
|
}
|
||||||
|
GameResponse::HostResponse { username, token } => {
|
||||||
|
ctx.username = username;
|
||||||
|
ctx.token = token;
|
||||||
|
return Some(StateTransition::JoinAsHost);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
warn!("Got unexpected message: {:?}", msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.retry_count += 1;
|
self.retry_count += 1;
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
use context::Context;
|
use context::Context;
|
||||||
|
use host_screen::HostScreen;
|
||||||
use login_screen::LoginScreen;
|
use login_screen::LoginScreen;
|
||||||
use macroquad::prelude::*;
|
use macroquad::prelude::*;
|
||||||
use player_screen::PlayerScreen;
|
use player_screen::PlayerScreen;
|
||||||
use screen::Screen;
|
use screen::Screen;
|
||||||
|
|
||||||
mod context;
|
mod context;
|
||||||
|
mod host_screen;
|
||||||
mod login_screen;
|
mod login_screen;
|
||||||
mod model;
|
mod model;
|
||||||
mod player_screen;
|
mod player_screen;
|
||||||
@ -14,15 +16,18 @@ pub enum State {
|
|||||||
Init,
|
Init,
|
||||||
Login(LoginScreen),
|
Login(LoginScreen),
|
||||||
PlayerScreen(PlayerScreen),
|
PlayerScreen(PlayerScreen),
|
||||||
|
HostScreen(HostScreen),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
pub fn transition_state(&self, transition: StateTransition) -> Self {
|
pub fn transition_state(&self, transition: StateTransition) -> Self {
|
||||||
|
info!("Applying transition: {:?}", transition);
|
||||||
match transition {
|
match transition {
|
||||||
StateTransition::Init | StateTransition::LeaveGame => {
|
StateTransition::Init | StateTransition::LeaveGame => {
|
||||||
Self::Login(LoginScreen::default())
|
Self::Login(LoginScreen::default())
|
||||||
}
|
}
|
||||||
StateTransition::JoinAsPlayer => Self::PlayerScreen(PlayerScreen::default()),
|
StateTransition::JoinAsPlayer => Self::PlayerScreen(PlayerScreen::default()),
|
||||||
|
StateTransition::JoinAsHost => Self::HostScreen(HostScreen::default()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,6 +36,7 @@ impl State {
|
|||||||
Self::Init => {}
|
Self::Init => {}
|
||||||
Self::Login(login_screen) => login_screen.handle_frame(ctx),
|
Self::Login(login_screen) => login_screen.handle_frame(ctx),
|
||||||
Self::PlayerScreen(player_screen) => player_screen.handle_frame(ctx),
|
Self::PlayerScreen(player_screen) => player_screen.handle_frame(ctx),
|
||||||
|
State::HostScreen(host_screen) => host_screen.handle_frame(ctx),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,13 +45,16 @@ impl State {
|
|||||||
Self::Init => Some(StateTransition::Init),
|
Self::Init => Some(StateTransition::Init),
|
||||||
State::Login(login_screen) => login_screen.handle_messages(ctx),
|
State::Login(login_screen) => login_screen.handle_messages(ctx),
|
||||||
State::PlayerScreen(player_screen) => player_screen.handle_messages(ctx),
|
State::PlayerScreen(player_screen) => player_screen.handle_messages(ctx),
|
||||||
|
State::HostScreen(host_screen) => host_screen.handle_messages(ctx),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum StateTransition {
|
pub enum StateTransition {
|
||||||
Init,
|
Init,
|
||||||
JoinAsPlayer,
|
JoinAsPlayer,
|
||||||
|
JoinAsHost,
|
||||||
LeaveGame,
|
LeaveGame,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ pub mod register;
|
|||||||
pub enum ClientMessage {
|
pub enum ClientMessage {
|
||||||
BuzzIn(BuzzIn),
|
BuzzIn(BuzzIn),
|
||||||
Register(Register),
|
Register(Register),
|
||||||
|
CreateRoom(Register),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
@ -22,7 +23,8 @@ pub struct ClientRequest {
|
|||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
pub enum GameResponse {
|
pub enum GameResponse {
|
||||||
JoinResponse { username: String, token: String },
|
JoinResponse { username: String, token: String },
|
||||||
|
HostResponse { username: String, token: String },
|
||||||
AckBuzzer,
|
AckBuzzer,
|
||||||
ResetBuzzer,
|
ResetBuzzer,
|
||||||
UpdatePountTotal { score: i32 },
|
UpdatePointTotal { score: i32 },
|
||||||
}
|
}
|
||||||
|
@ -28,10 +28,11 @@ impl Screen for PlayerScreen {
|
|||||||
info!("Button press reset");
|
info!("Button press reset");
|
||||||
self.button_pressed = false;
|
self.button_pressed = false;
|
||||||
}
|
}
|
||||||
crate::model::GameResponse::UpdatePountTotal { score } => {
|
crate::model::GameResponse::UpdatePointTotal { score } => {
|
||||||
info!("Score updated to: {}", score);
|
info!("Score updated to: {}", score);
|
||||||
self.score = score;
|
self.score = score;
|
||||||
}
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
21
shared/src/player_session.gleam
Normal file
21
shared/src/player_session.gleam
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import gleam/dynamic/decode
|
||||||
|
import gleam/json
|
||||||
|
|
||||||
|
pub type PlayerSession {
|
||||||
|
PlayerSession(id: Int, token_hash: String, username: String)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn encode_player_session(player_session: PlayerSession) -> json.Json {
|
||||||
|
json.object([
|
||||||
|
#("id", json.int(player_session.id)),
|
||||||
|
#("token_hash", json.string(player_session.token_hash)),
|
||||||
|
#("username", json.string(player_session.username)),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn player_session_decoder() -> decode.Decoder(PlayerSession) {
|
||||||
|
use id <- decode.field("id", decode.int)
|
||||||
|
use token_hash <- decode.field("token_hash", decode.string)
|
||||||
|
use username <- decode.field("username", decode.string)
|
||||||
|
decode.success(PlayerSession(id:, token_hash:, username:))
|
||||||
|
}
|
@ -4,12 +4,12 @@ import gleam/list
|
|||||||
import player
|
import player
|
||||||
|
|
||||||
pub type Session {
|
pub type Session {
|
||||||
Session(id: Int, host_user_id: Int, players: List(player.Player))
|
Session(id: String, host_user_id: Int, players: List(player.Player))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn serialize(session: Session) -> json.Json {
|
pub fn serialize(session: Session) -> json.Json {
|
||||||
json.object([
|
json.object([
|
||||||
#("id", json.int(session.id)),
|
#("id", json.string(session.id)),
|
||||||
#("host_user_id", json.int(session.host_user_id)),
|
#("host_user_id", json.int(session.host_user_id)),
|
||||||
#(
|
#(
|
||||||
"players",
|
"players",
|
||||||
@ -22,7 +22,7 @@ pub fn serialize(session: Session) -> json.Json {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn decoder() -> decode.Decoder(Session) {
|
pub fn decoder() -> decode.Decoder(Session) {
|
||||||
use id <- decode.field("id", decode.int)
|
use id <- decode.field("id", decode.string)
|
||||||
use host_user_id <- decode.field("host_user_id", decode.int)
|
use host_user_id <- decode.field("host_user_id", decode.int)
|
||||||
use players <- decode.field("players", decode.list(player.decoder()))
|
use players <- decode.field("players", decode.list(player.decoder()))
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user