DEVELOPMENT ENVIRONMENT

~liljamo/deck-builder

00f0dd3e957efb3c1cc145fca09c5c4785a9ebf2 — Jonni Liljamo 1 year, 8 months ago 82d5b7c
feat!(client, server, api, shared): revamp data requests!
 * now even more generic than before
 * also implemented GET game/{id}
 * GET game/all_forming and game/my_games now return only ids
 * client now has a way to cache basic information about all games
   fetched in the play screen, reducing the amount of data transferred
M api/src/actions/game/all_forming.rs => api/src/actions/game/all_forming.rs +7 -2
@@ 13,7 13,7 @@ use laurelin_shared::{
    types::game::{Game, GAMESTATE_FORMING},
};

pub(crate) fn all_forming(conn: &mut PgConnection) -> Result<Vec<Game>, APIError> {
pub(crate) fn all_forming(conn: &mut PgConnection) -> Result<Vec<String>, APIError> {
    let games_res = games::table
        .filter(games::state.eq(GAMESTATE_FORMING))
        .load::<Game>(conn);


@@ 23,5 23,10 @@ pub(crate) fn all_forming(conn: &mut PgConnection) -> Result<Vec<Game>, APIError
        Ok(games) => games,
    };

    Ok(games)
    let mut ids = Vec::with_capacity(games.len());
    for game in &games {
        ids.push(game.id.to_string());
    }

    Ok(ids)
}

A api/src/actions/game/info.rs => api/src/actions/game/info.rs +26 -0
@@ 0,0 1,26 @@
/*
 * This file is part of laurelin/api
 * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
 *
 * Licensed under GPL-3.0-only.
 * See LICENSE for licensing information.
 */

use diesel::{ExpressionMethods, PgConnection, QueryDsl, RunQueryDsl};
use laurelin_schema::schema::games;
use laurelin_shared::{error::api::APIError, types::game::Game};
use uuid::Uuid;

pub(crate) fn info(conn: &mut PgConnection, game_id: &str) -> Result<Game, APIError> {
    let game = match games::table
        .filter(games::id.eq(&Uuid::try_parse(game_id).unwrap())) // TODO: handle
        .first::<Game>(conn)
    {
        Err(_) => {
            return Err(APIError::UserNotFound);
        }
        Ok(game) => game,
    };

    Ok(game)
}

M api/src/actions/game/mod.rs => api/src/actions/game/mod.rs +3 -0
@@ 17,3 17,6 @@ pub(crate) use my_games::my_games;

mod patch;
pub(crate) use patch::patch;

mod info;
pub(crate) use info::info;

M api/src/actions/game/my_games.rs => api/src/actions/game/my_games.rs +7 -2
@@ 11,7 11,7 @@ use laurelin_schema::schema::games;
use laurelin_shared::{error::api::APIError, types::game::Game};
use uuid::Uuid;

pub(crate) fn my_games(conn: &mut PgConnection, user_id: &str) -> Result<Vec<Game>, APIError> {
pub(crate) fn my_games(conn: &mut PgConnection, user_id: &str) -> Result<Vec<String>, APIError> {
    let games_res = games::table
        .filter(
            games::host_id


@@ 25,5 25,10 @@ pub(crate) fn my_games(conn: &mut PgConnection, user_id: &str) -> Result<Vec<Gam
        Ok(games) => games,
    };

    Ok(games)
    let mut ids = Vec::with_capacity(games.len());
    for game in &games {
        ids.push(game.id.to_string());
    }

    Ok(ids)
}

M api/src/handlers/game/mod.rs => api/src/handlers/game/mod.rs +26 -0
@@ 90,6 90,32 @@ async fn my_games(session: Session, pool: web::Data<PgPool>) -> impl Responder {
    }
}

#[get("/api/game/{id}")]
async fn info(session: Session, pool: web::Data<PgPool>, id: web::Path<String>) -> impl Responder {
    let session_validation = session::validate_session(&session);

    match session_validation {
        Err(err) => err,
        Ok(_user_id) => {
            let game = web::block(move || {
                let mut conn = match pool.get() {
                    Err(_) => return Err(APIError::DatabasePoolGetFailed),
                    Ok(conn) => conn,
                };
                actions::game::info(&mut conn, &id)
            })
            .await;
            match game {
                Err(_err) => HttpResponse::InternalServerError().json(APIError::Undefined),
                Ok(game_res) => match game_res {
                    Err(err) => HttpResponse::InternalServerError().body(err.to_string()),
                    Ok(game) => HttpResponse::Ok().json(game),
                },
            }
        }
    }
}

#[patch("/api/game/{id}")]
async fn patch(
    pool: web::Data<PgPool>,

M api/src/main.rs => api/src/main.rs +1 -0
@@ 103,6 103,7 @@ async fn main() -> std::io::Result<()> {
            .service(handlers::game::create)
            .service(handlers::game::all_forming)
            .service(handlers::game::my_games)
            .service(handlers::game::info)
            .service(handlers::game::patch)
    })
    .bind(("0.0.0.0", 8080))?

M client/src/main.rs => client/src/main.rs +9 -1
@@ 11,6 11,7 @@
use bevy::{
    app::AppExit,
    prelude::*,
    utils::HashMap,
    window::{
        CompositeAlphaMode, Cursor, PresentMode, WindowLevel, WindowMode, WindowResizeConstraints,
        WindowResolution,


@@ 21,7 22,10 @@ use naia_bevy_client::{
    Client, ClientConfig as NaiaClientConfig, Plugin as NaiaClientPlugin, ReceiveEvents,
};

use laurelin_shared::{server::protocol::protocol, types::user::UserPub};
use laurelin_shared::{
    server::protocol::protocol,
    types::{game::GamePub, user::UserPub},
};

mod cfg;
mod constants;


@@ 51,6 55,8 @@ pub struct Global {
    pub users_cache: Vec<UserPub>,
    /// stores ids of users currently in queue to be gotten
    pub users_cache_queue: Vec<String>,
    pub games_cache: HashMap<String, GamePub>,
    pub games_cache_queue: Vec<String>,
}

#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]


@@ 105,6 111,8 @@ fn main() {
    app.insert_resource(Global {
        users_cache: vec![],
        users_cache_queue: vec![],
        games_cache: HashMap::new(),
        games_cache_queue: vec![],
    })
    .insert_resource(cfg::CfgDirs(
        directories::ProjectDirs::from("com", "liljamo", "deckbuilder")

M client/src/plugins/menu/ui/play/ui.rs => client/src/plugins/menu/ui/play/ui.rs +47 -17
@@ 9,7 9,7 @@
use bevy::prelude::*;
use bevy_egui::{egui, EguiContexts};
use laurelin_shared::types::{
    game::{GAMESTATE_FINISHED, GAMESTATE_INPROGRESS},
    game::{GamePub, GAMESTATE_FINISHED, GAMESTATE_FORMING, GAMESTATE_INPROGRESS},
    user::UserPub,
};



@@ 110,10 110,10 @@ pub fn ui(
                            browse_forming(ui, &mut data, &cfg_user, &global);
                        }
                        PlayScreenBrowseState::InProgress => {
                            browse_inprogress(ui, &mut data, &global);
                            browse_inprogress(ui, &mut data, &global, &cfg_user);
                        }
                        PlayScreenBrowseState::Finished => {
                            browse_finished(ui, &mut data, &global);
                            browse_finished(ui, &mut data, &global, &cfg_user);
                        }
                    });
                });


@@ 169,10 169,16 @@ fn browse_forming(
        return;
    }

    if data.all_forming.is_empty() {
    let all_forming: Vec<&GamePub> = global
        .games_cache
        .values()
        .filter(|g| g.state == GAMESTATE_FORMING)
        .collect();

    if all_forming.is_empty() {
        ui.label("No forming games found.");
    } else {
        for game in data.all_forming.clone() {
        for game in all_forming {
            egui::Frame::none()
                .fill(egui::Color32::BLACK)
                .rounding(4.)


@@ 220,7 226,12 @@ fn browse_forming(
    }
}

fn browse_inprogress(ui: &mut egui::Ui, data: &mut PlayScreenData, global: &Global) {
fn browse_inprogress(
    ui: &mut egui::Ui,
    data: &mut PlayScreenData,
    global: &Global,
    cfg_user: &CfgUser,
) {
    if data.waiting_for_my_games {
        ui.horizontal(|ui| {
            ui.spinner();


@@ 229,13 240,20 @@ fn browse_inprogress(ui: &mut egui::Ui, data: &mut PlayScreenData, global: &Glob
        return;
    }

    if data.my_games.is_empty() {
    let my_games_inprogress: Vec<&GamePub> = global
        .games_cache
        .values()
        .filter(|g| {
            g.state == GAMESTATE_INPROGRESS
                && (g.host_id == cfg_user.id
                    || g.guest_id.clone().unwrap_or("".to_string()) == cfg_user.id)
        })
        .collect();

    if my_games_inprogress.is_empty() {
        ui.label("No games found.");
    } else {
        let mut games = data.my_games.clone();
        games.retain(|g| g.state == GAMESTATE_INPROGRESS);

        for game in games {
        for game in my_games_inprogress {
            egui::Frame::none()
                .fill(egui::Color32::BLACK)
                .rounding(4.)


@@ 271,7 289,12 @@ fn browse_inprogress(ui: &mut egui::Ui, data: &mut PlayScreenData, global: &Glob
    }
}

fn browse_finished(ui: &mut egui::Ui, data: &mut PlayScreenData, global: &Global) {
fn browse_finished(
    ui: &mut egui::Ui,
    data: &mut PlayScreenData,
    global: &Global,
    cfg_user: &CfgUser,
) {
    if data.waiting_for_my_games {
        ui.horizontal(|ui| {
            ui.spinner();


@@ 280,13 303,20 @@ fn browse_finished(ui: &mut egui::Ui, data: &mut PlayScreenData, global: &Global
        return;
    }

    if data.my_games.is_empty() {
    let my_games_finished: Vec<&GamePub> = global
        .games_cache
        .values()
        .filter(|g| {
            g.state == GAMESTATE_FINISHED
                && (g.host_id == cfg_user.id
                    || g.guest_id.clone().unwrap_or("".to_string()) == cfg_user.id)
        })
        .collect();

    if my_games_finished.is_empty() {
        ui.label("No games found.");
    } else {
        let mut games = data.my_games.clone();
        games.retain(|g| g.state == GAMESTATE_FINISHED);

        for game in games {
        for game in my_games_finished {
            egui::Frame::none()
                .fill(egui::Color32::BLACK)
                .rounding(4.)

M client/src/plugins/networking/mod.rs => client/src/plugins/networking/mod.rs +2 -0
@@ 19,11 19,13 @@ impl Plugin for NetworkingPlugin {
        app.add_event::<send::game::GameCreateEvent>()
            .add_event::<send::game::GameAllFormingEvent>()
            .add_event::<send::game::GameMyGamesEvent>()
            .add_event::<send::game::GameInfoEvent>()
            .add_event::<send::user::PubUserDetailsEvent>()
            .add_systems((
                send::game::create_event,
                send::game::all_forming_event,
                send::game::my_games_event,
                send::game::game_info_event,
                send::user::pub_user_details_event,
            ))
            .add_systems(

M client/src/plugins/networking/systems/events/receive/mod.rs => client/src/plugins/networking/systems/events/receive/mod.rs +42 -24
@@ 7,9 7,12 @@
 */

use bevy::prelude::*;
use laurelin_shared::server::{
    channels::{AfterAuthChannel, CookieRefreshChannel, DataRequestChannel},
    messages::{AfterAuth, CookieRefresh, DataRequestResponse, DataRequestType},
use laurelin_shared::{
    server::{
        channels::{AfterAuthChannel, CookieRefreshChannel, DataRequestChannel},
        messages::{AfterAuth, CookieRefresh, DataRequestResponse, DataRequestType},
    },
    types::{game::GamePub, user::UserPub},
};
use naia_bevy_client::{
    events::{ClientTickEvent, ConnectEvent, DisconnectEvent, MessageEvents, RejectEvent},


@@ 28,7 31,7 @@ use crate::{
    Global,
};

use super::send::user::PubUserDetailsEvent;
use super::send::{game::GameInfoEvent, user::PubUserDetailsEvent};

pub fn connect_events(mut ev: EventReader<ConnectEvent>, client: Client) {
    for _ in ev.iter() {


@@ 61,6 64,7 @@ pub fn message_events(
    mut commands: Commands,
    mut ev: EventReader<MessageEvents>,
    mut pud_ev_w: EventWriter<PubUserDetailsEvent>,
    mut gi_ev_w: EventWriter<GameInfoEvent>,
    mut global: ResMut<Global>,
    mut cfg_user: ResMut<CfgUser>,
    mut connect_data: ResMut<ConnectScreenData>,


@@ 89,45 93,59 @@ pub fn message_events(
        for response in events.read::<DataRequestChannel, DataRequestResponse>() {
            match DataRequestType::from_u8(&response.r#type) {
                DataRequestType::GameCreate => {
                    // TODO: handle possible error (unwrap,
                    // TODO: handle possible errors (unwrap,
                    //       and if the response data is an error)
                    play_data.cur_game = response.games.unwrap().get(0).cloned();
                    let game =
                        serde_json::from_str(response.data.unwrap().get(0).unwrap()).unwrap();
                    play_data.cur_game = Some(game);
                    play_data.state = PlayScreenState::InLobbyHost;
                }
                DataRequestType::GameAllForming => {
                    // TODO: handle possible error
                    let all_forming = response.games.unwrap();
                    // TODO: handle
                    let all_forming_ids = response.data.unwrap();

                    for game in &all_forming {
                        pud_ev_w.send(PubUserDetailsEvent {
                            id: game.host_id.clone(),
                    for game_id in &all_forming_ids {
                        gi_ev_w.send(GameInfoEvent {
                            id: game_id.clone(),
                        });
                    }

                    play_data.all_forming = all_forming;
                    play_data.waiting_for_all_forming = false;
                }
                DataRequestType::GameMyGames => {
                    // TODO: handle possible error
                    let my_games = response.games.unwrap();
                    // TODO: handle
                    let my_games_ids = response.data.unwrap();

                    for game in &my_games {
                        pud_ev_w.send(PubUserDetailsEvent {
                            id: game.host_id.clone(),
                    for game_id in &my_games_ids {
                        gi_ev_w.send(GameInfoEvent {
                            id: game_id.clone(),
                        });
                        if let Some(guest_id) = &game.guest_id {
                            pud_ev_w.send(PubUserDetailsEvent {
                                id: guest_id.clone(),
                            });
                        }
                    }

                    play_data.my_games = my_games;
                    play_data.waiting_for_my_games = false;
                }
                DataRequestType::GameInfo => {
                    // TODO: handle
                    let game: GamePub =
                        serde_json::from_str(response.data.unwrap().get(0).unwrap()).unwrap();

                    // cache players of the game
                    pud_ev_w.send(PubUserDetailsEvent {
                        id: game.host_id.clone(),
                    });
                    if let Some(guest_id) = &game.guest_id {
                        pud_ev_w.send(PubUserDetailsEvent {
                            id: guest_id.clone(),
                        });
                    }

                    global.games_cache_queue.retain(|id| id != &game.id);
                    global.games_cache.insert(game.id.clone(), game);
                }
                DataRequestType::PubUserDetails => {
                    // TODO: handle possible error
                    let user = response.users.unwrap().get(0).unwrap().clone();
                    let user: UserPub =
                        serde_json::from_str(response.data.unwrap().get(0).unwrap()).unwrap();
                    global.users_cache_queue.retain(|id| id != &user.id);
                    global.users_cache.push(user);
                }

M client/src/plugins/networking/systems/events/send/game.rs => client/src/plugins/networking/systems/events/send/game.rs +40 -1
@@ 6,13 6,15 @@
 * See LICENSE for licensing information.
 */

use bevy::prelude::EventReader;
use bevy::prelude::{EventReader, ResMut};
use laurelin_shared::server::{
    channels::DataRequestChannel,
    messages::{DataRequest, DataRequestType},
};
use naia_bevy_client::Client;

use crate::Global;

use super::data_request;

pub struct GameCreateEvent;


@@ 38,3 40,40 @@ data_request!(
    DataRequestType::GameMyGames,
    |ev| None
);

pub struct GameInfoEvent {
    pub id: String,
}

pub fn game_info_event(
    mut ev: EventReader<GameInfoEvent>,
    mut client: Client,
    mut global: ResMut<Global>,
) {
    for ev in ev.iter() {
        // check if already in cache OR in cache queue
        if global.games_cache.contains_key(&ev.id) {
            return;
        }

        if !global
            .games_cache_queue
            .iter()
            .filter(|&id| id == &ev.id)
            .cloned()
            .collect::<Vec<String>>()
            .is_empty()
        {
            return;
        }

        // add to cache queue
        global.games_cache_queue.push(ev.id.clone());

        // send request
        client.send_message::<DataRequestChannel, DataRequest>(&DataRequest::new(
            DataRequestType::GameInfo as u8,
            Some(ev.id.clone()),
        ));
    }
}

M server/src/systems/event/message/mod.rs => server/src/systems/event/message/mod.rs +49 -21
@@ 6,16 6,16 @@
 * See LICENSE for licensing information.
 */

use std::vec;

use bevy_ecs::{
    event::EventReader,
    system::{Res, ResMut},
};
use laurelin_shared::{
    api::{
        self,
        game::{
            all_forming, create, my_games, ResponseAllForming, ResponseCreateGame, ResponseMyGames,
            all_forming, create, my_games, ResponseAllForming, ResponseCreateGame,
            ResponseGameInfo, ResponseMyGames,
        },
        user::{self, ResponseInfo},
    },


@@ 46,13 46,15 @@ pub(crate) fn message_events(
                        ResponseCreateGame::Error(_err) => None, // TODO: handle
                        ResponseCreateGame::Valid(result) => Some(result),
                    };
                    let mut games_pub = vec![];
                    if let Some(game) = game {
                        games_pub.push(GamePub::from_game(&game));
                    }
                    let game_pub_json = match game {
                        None => {
                            panic!("I can't be bothered to handle this right now.")
                        }
                        Some(game) => serde_json::to_string(&GamePub::from_game(&game)).unwrap(),
                    };
                    server.send_message::<DataRequestChannel, DataRequestResponse>(
                        &user_key,
                        &DataRequestResponse::new(request.r#type, None, Some(games_pub)),
                        &DataRequestResponse::new(request.r#type, Some(vec![game_pub_json])),
                    );

                    // update cookie


@@ 68,17 70,13 @@ pub(crate) fn message_events(
                    // TODO: handle
                    let cookie = global.user_to_session_map.get(&user_key).unwrap();
                    let wrapped = all_forming(&config.api_address, cookie);
                    let games = match wrapped.response {
                    let game_ids = match wrapped.response {
                        ResponseAllForming::Error(_err) => vec![], // TODO: handle
                        ResponseAllForming::Valid(result) => result,
                    };
                    let mut games_pub = vec![];
                    for game in games {
                        games_pub.push(GamePub::from_game(&game));
                    }
                    server.send_message::<DataRequestChannel, DataRequestResponse>(
                        &user_key,
                        &DataRequestResponse::new(request.r#type, None, Some(games_pub)),
                        &DataRequestResponse::new(request.r#type, Some(game_ids)),
                    );

                    // update cookie


@@ 94,17 92,45 @@ pub(crate) fn message_events(
                    // TODO: handle
                    let cookie = global.user_to_session_map.get(&user_key).unwrap();
                    let wrapped = my_games(&config.api_address, cookie);
                    let games = match wrapped.response {
                    let game_ids = match wrapped.response {
                        ResponseMyGames::Error(_err) => vec![], // TODO: handle
                        ResponseMyGames::Valid(result) => result,
                    };
                    let mut games_pub = vec![];
                    for game in games {
                        games_pub.push(GamePub::from_game(&game));
                    }
                    server.send_message::<DataRequestChannel, DataRequestResponse>(
                        &user_key,
                        &DataRequestResponse::new(request.r#type, None, Some(games_pub)),
                        &DataRequestResponse::new(request.r#type, Some(game_ids)),
                    );

                    // update cookie
                    global
                        .user_to_session_map
                        .insert(user_key, wrapped.cookie.clone());
                    server.send_message::<CookieRefreshChannel, CookieRefresh>(
                        &user_key,
                        &CookieRefresh::new(&wrapped.cookie),
                    );
                }
                DataRequestType::GameInfo => {
                    // TODO: handle
                    let cookie = global.user_to_session_map.get(&user_key).unwrap();
                    let wrapped = api::game::info(
                        &config.api_address,
                        cookie,
                        &request.data.unwrap_or("".to_string()),
                    );
                    let game = match wrapped.response {
                        ResponseGameInfo::Error(_err) => None, // TODO: handle
                        ResponseGameInfo::Valid(result) => Some(result),
                    };
                    let game_pub_json = match game {
                        None => {
                            panic!("I can't be bothered to handle this right now.")
                        }
                        Some(game) => serde_json::to_string(&GamePub::from_game(&game)).unwrap(),
                    };
                    server.send_message::<DataRequestChannel, DataRequestResponse>(
                        &user_key,
                        &DataRequestResponse::new(request.r#type, Some(vec![game_pub_json])),
                    );

                    // update cookie


@@ 130,9 156,11 @@ pub(crate) fn message_events(
                        }
                        ResponseInfo::Ok(result) => result,
                    };
                    // TODO: handle
                    let user_details_json = serde_json::to_string(&user_details).unwrap();
                    server.send_message::<DataRequestChannel, DataRequestResponse>(
                        &user_key,
                        &DataRequestResponse::new(request.r#type, Some(vec![user_details]), None),
                        &DataRequestResponse::new(request.r#type, Some(vec![user_details_json])),
                    );

                    // update cookie

M shared/src/api/game/all_forming.rs => shared/src/api/game/all_forming.rs +2 -2
@@ 9,9 9,9 @@
use reqwest::{self, header::COOKIE};
use serde::{Deserialize, Serialize};

use crate::{api::macros::extract_cookie, error::api::APIError, types::game::Game};
use crate::{api::macros::extract_cookie, error::api::APIError};

pub type ResultAllForming = Vec<Game>;
pub type ResultAllForming = Vec<String>;

#[derive(Serialize, Deserialize)]
#[serde(untagged)]

M shared/src/api/game/info.rs => shared/src/api/game/info.rs +19 -10
@@ 6,26 6,35 @@
 * See LICENSE for licensing information.
 */

use reqwest;
use reqwest::{self, header::COOKIE};
use serde::{Deserialize, Serialize};

use super::{types, APIErrorWrapper};
use crate::{api::macros::extract_cookie, error::api::APIError, types::game::Game};

#[derive(Debug, Serialize, Deserialize)]
#[derive(Serialize, Deserialize)]
#[serde(untagged)]
pub enum ResponseInfo {
    Error(APIErrorWrapper),
    Valid(types::Game),
pub enum ResponseGameInfo {
    Error(APIError),
    Valid(Game),
}

pub fn info(api_address: String, token: String, game_id: String) -> ResponseInfo {
#[derive(Deserialize)]
pub struct ResponseGameInfoWrapper {
    pub response: ResponseGameInfo,
    pub cookie: String,
}

pub fn info(api_address: &String, cookie: &String, game_id: &String) -> ResponseGameInfoWrapper {
    let client = reqwest::blocking::Client::new();

    let resp = client
        .get(&format!("{}/game/{}", api_address, game_id))
        .header("Authorization", token)
        .get(format!("{}/game/{}", api_address, game_id))
        .header(COOKIE, &format!("id={}", cookie))
        .send()
        .unwrap();

    resp.json().unwrap()
    ResponseGameInfoWrapper {
        cookie: extract_cookie!(resp),
        response: resp.json().unwrap(),
    }
}

M shared/src/api/game/mod.rs => shared/src/api/game/mod.rs +2 -2
@@ 12,8 12,8 @@ pub use create::*;
mod all_forming;
pub use all_forming::*;

//mod info;
//pub use info::*;
mod info;
pub use info::*;

//mod join;
//pub use join::*;

M shared/src/api/game/mygames.rs => shared/src/api/game/mygames.rs +2 -2
@@ 9,9 9,9 @@
use reqwest::{self, header::COOKIE};
use serde::{Deserialize, Serialize};

use crate::{api::macros::extract_cookie, error::api::APIError, types::game::Game};
use crate::{api::macros::extract_cookie, error::api::APIError};

pub type ResultMyGames = Vec<Game>;
pub type ResultMyGames = Vec<String>;

#[derive(Serialize, Deserialize)]
#[serde(untagged)]

M shared/src/server/messages/datarequest.rs => shared/src/server/messages/datarequest.rs +5 -10
@@ 8,8 8,6 @@

use naia_bevy_shared::Message;

use crate::types::{game::GamePub, user::UserPub};

#[derive(Message)]
pub struct DataRequest {
    pub r#type: u8,


@@ 29,17 27,12 @@ impl DataRequest {
#[derive(Message)]
pub struct DataRequestResponse {
    pub r#type: u8,
    pub users: Option<Vec<UserPub>>,
    pub games: Option<Vec<GamePub>>,
    pub data: Option<Vec<String>>,
}

impl DataRequestResponse {
    pub fn new(r#type: u8, users: Option<Vec<UserPub>>, games: Option<Vec<GamePub>>) -> Self {
        Self {
            r#type,
            users,
            games,
        }
    pub fn new(r#type: u8, data: Option<Vec<String>>) -> Self {
        Self { r#type, data }
    }
}



@@ 48,6 41,7 @@ pub enum DataRequestType {
    GameAllForming = 100,
    GameMyGames = 101,
    GameCreate = 102,
    GameInfo = 103,
    PubUserDetails = 150,
}



@@ 58,6 52,7 @@ impl DataRequestType {
            100 => Self::GameAllForming,
            101 => Self::GameMyGames,
            102 => Self::GameCreate,
            103 => Self::GameInfo,
            150 => Self::PubUserDetails,
            _ => {
                // NOTE/TODO/FIXME: bad, veeeery bad.