From 87b3125f8e463ae0244ad3dc80bbee078cb24c4e Mon Sep 17 00:00:00 2001 From: Joshua Higgins Date: Sat, 12 Oct 2024 14:57:54 -0400 Subject: [PATCH] Adding servers, fetching servers on the client --- auth/src/server.rs | 14 +- auth/src/types.rs | 4 +- client/src/app.rs | 195 ++++++++++++++++++---------- client/src/main.rs | 2 +- client/src/ui/{panels.rs => gui.rs} | 165 ++++++++++++++++------- client/src/ui/mod.rs | 2 +- 6 files changed, 262 insertions(+), 120 deletions(-) rename client/src/ui/{panels.rs => gui.rs} (72%) diff --git a/auth/src/server.rs b/auth/src/server.rs index a1e187a..1564a69 100644 --- a/auth/src/server.rs +++ b/auth/src/server.rs @@ -507,11 +507,13 @@ impl RealmAuth for RealmAuthServer { } } - async fn add_server(self, _: Context, username: String, token: String, domain: String) -> Result<(), ErrorCode> { + async fn add_server(self, _: Context, username: String, token: String, domain: String, port: u16) -> Result<(), ErrorCode> { if !self.is_authorized(&username, &token).await? { return Err(Unauthorized); } + let address = format!("{}:{}", domain, port); + let result = query!("SELECT servers FROM user WHERE username = ?", username).fetch_one(&self.db_pool).await; match result { Ok(row) => { @@ -523,12 +525,12 @@ impl RealmAuth for RealmAuthServer { } }; for server in &vec_servers { - if server.eq(&domain) { + if server.eq(&address) { return Err(AlreadyJoinedServer); } } - vec_servers.push(&domain); + vec_servers.push(&address); let new_servers = vec_servers.join("|"); let result = query!("UPDATE user SET servers = ? WHERE username = ?", new_servers, username).fetch_one(&self.db_pool).await; @@ -541,11 +543,13 @@ impl RealmAuth for RealmAuthServer { } } - async fn remove_server(self, _: Context, username: String, token: String, domain: String) -> Result<(), ErrorCode> { + async fn remove_server(self, _: Context, username: String, token: String, domain: String, port: u16) -> Result<(), ErrorCode> { if !self.is_authorized(&username, &token).await? { return Err(Unauthorized); } + let address = format!("{}:{}", domain, port); + let result = query!("SELECT servers FROM user WHERE username = ?", username).fetch_one(&self.db_pool).await; match result { Ok(row) => { @@ -557,7 +561,7 @@ impl RealmAuth for RealmAuthServer { } }; for i in 0..vec_servers.len() { - if vec_servers.get(i).unwrap().eq(&domain) { + if vec_servers.get(i).unwrap().eq(&address) { vec_servers.remove(i); let new_servers = vec_servers.join("|"); diff --git a/auth/src/types.rs b/auth/src/types.rs index 0af42fa..dc350e1 100644 --- a/auth/src/types.rs +++ b/auth/src/types.rs @@ -18,8 +18,8 @@ pub trait RealmAuth { async fn sign_out(username: String, token: String) -> Result<(), ErrorCode>; async fn delete_account_flow(username: String, token: String) -> Result<(), ErrorCode>; async fn finish_delete_account_flow(username: String, token: String, login_code: u32) -> Result<(), ErrorCode>; - async fn add_server(username: String, token: String, domain: String) -> Result<(), ErrorCode>; - async fn remove_server(username: String, token: String, domain: String) -> Result<(), ErrorCode>; + async fn add_server(username: String, token: String, domain: String, port: u16) -> Result<(), ErrorCode>; + async fn remove_server(username: String, token: String, domain: String, port: u16) -> Result<(), ErrorCode>; async fn get_joined_servers(username: String, token: String) -> Result, ErrorCode>; //NOTE: Anyone can call diff --git a/client/src/app.rs b/client/src/app.rs index 160cf31..d8acede 100644 --- a/client/src/app.rs +++ b/client/src/app.rs @@ -7,20 +7,25 @@ use realm_auth::types::RealmAuthClient; use realm_shared::types::ErrorCode::*; use realm_shared::types::ErrorCode; use crate::types::ClientUser; -use crate::ui::panels; +use crate::ui::gui; /// We derive Deserialize/Serialize so we can persist app state on shutdown. #[derive(serde::Deserialize, serde::Serialize)] #[serde(default)] // if we add new fields, give them default values when deserializing old state -pub struct TemplateApp { +pub struct RealmApp { // Example stuff: pub label: String, pub selected: bool, pub selected_serverid: Option, pub selected_roomid: Option, - + + #[serde(skip)] pub current_user: Option, + pub saved_username: Option, + pub saved_token: Option, + pub saved_auth_address: Option, + #[serde(skip)] pub value: f32, #[serde(skip)] @@ -33,23 +38,33 @@ pub struct TemplateApp { pub login_window_server_address: String, #[serde(skip)] pub login_window_email: String, - + #[serde(skip)] pub login_ready_for_code_input: bool, #[serde(skip)] pub signup_window_open: bool, - + + #[serde(skip)] + pub server_window_open: bool, + #[serde(skip)] + pub server_window_domain: String, + #[serde(skip)] + pub server_window_port: String, + #[serde(skip)] pub login_start_channel: (Sender>, Receiver>), #[serde(skip)] pub login_ending_channel: (Sender>, Receiver>), - + #[serde(skip)] pub fetching_user_data_channel: (Sender>, Receiver>), + + #[serde(skip)] + pub added_server_channel: (Sender>, Receiver>), } -impl Default for TemplateApp { +impl Default for RealmApp { fn default() -> Self { Self { // Example stuff: @@ -58,8 +73,11 @@ impl Default for TemplateApp { selected_serverid: None, selected_roomid: None, current_user: None, + saved_username: None, + saved_token: None, + saved_auth_address: None, value: 2.7, - + login_window_open: false, login_window_username: String::new(), login_window_code: String::new(), @@ -68,15 +86,21 @@ impl Default for TemplateApp { login_ending_channel: broadcast::channel(10), login_ready_for_code_input: false, login_window_email: String::new(), - + signup_window_open: false, - + + server_window_open: false, + server_window_domain: String::new(), + server_window_port: "5051".to_string(), + fetching_user_data_channel: broadcast::channel(10), + + added_server_channel: broadcast::channel(10), } } } -impl TemplateApp { +impl RealmApp { /// Called once before the first frame. pub fn new(cc: &eframe::CreationContext<'_>) -> Self { // This is also where you can customize the look and feel of egui using @@ -92,12 +116,73 @@ impl TemplateApp { } } -impl eframe::App for TemplateApp { +pub fn fetch_user_data(send_channel: Sender>, server_address: String, username: String, token: String) { + let _handle = tokio::spawn(async move { + let mut transport = tarpc::serde_transport::tcp::connect(&server_address, Json::default); + transport.config_mut().max_frame_length(usize::MAX); + + let result = transport.await; + let connection = match result { + Ok(connection) => connection, + Err(e) => { + tracing::error!("Failed to connect to server: {}", e); + return; + } + }; + + let client = RealmAuthClient::new(tarpc::client::Config::default(), connection).spawn(); + let result = client.get_all_data(context::current(), username, token.clone()).await; + + match result { + Ok(r) => { + if let Err(code) = r { + send_channel.send(Err(code)).unwrap(); + } else { + let auth_user = r.unwrap(); + let servers: Vec = { + if auth_user.servers.eq("") { + Vec::new() + } else { + auth_user.servers.split('|').map(|s| s.to_string()).collect() + } + }; + send_channel.send(Ok(ClientUser { + id: auth_user.id, + server_address, + username: auth_user.username, + email: auth_user.email, + //avatar: auth_user.avatar, + servers, + token, + })).unwrap(); + } + } + Err(_) => { + send_channel.send(Err(RPCError)).unwrap(); + } + }; + }); +} + +impl eframe::App for RealmApp { /// Called each time the UI needs repainting, which may be many times per second. fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { // Put your widgets into a `SidePanel`, `TopBottomPanel`, `CentralPanel`, `Window` or `Area`. // For inspiration and more examples, go to https://emilk.github.io/egui + // Init at launch and refresh our user data from the auth server + if self.current_user.is_none() && + self.saved_token.is_some() && + self.saved_auth_address.is_some() && + self.saved_username.is_some() + { + let send_channel = self.fetching_user_data_channel.0.clone(); + let server_address = self.saved_auth_address.clone().unwrap(); + let username = self.saved_username.clone().unwrap(); + let token = self.saved_token.clone().unwrap(); + fetch_user_data(send_channel, server_address, username, token); + } + while let Ok(result) = self.login_start_channel.1.try_recv() { match result { Ok(_) => self.login_ready_for_code_input = true, @@ -118,53 +203,13 @@ impl eframe::App for TemplateApp { let send_channel = self.fetching_user_data_channel.0.clone(); let server_address = self.login_window_server_address.clone(); let username = self.login_window_username.clone(); + + self.saved_token = Some(token.clone()); + self.saved_username = Some(username.clone()); + self.saved_auth_address = Some(server_address.clone()); - let _handle = tokio::spawn(async move { - let mut transport = tarpc::serde_transport::tcp::connect(&server_address, Json::default); - transport.config_mut().max_frame_length(usize::MAX); - - let result = transport.await; - let connection = match result { - Ok(connection) => connection, - Err(e) => { - tracing::error!("Failed to connect to server: {}", e); - return; - } - }; - - let client = RealmAuthClient::new(tarpc::client::Config::default(), connection).spawn(); - let result = client.get_all_data(context::current(), username, token.clone()).await; - - match result { - Ok(r) => { - if let Err(code) = r { - send_channel.send(Err(code)).unwrap(); - } else { - let auth_user = r.unwrap(); - let servers: Vec = { - if auth_user.servers.eq("") { - Vec::new() - } else { - auth_user.servers.split('|').map(|s| s.to_string()).collect() - } - }; - send_channel.send(Ok(ClientUser { - id: auth_user.id, - server_address, - username: auth_user.username, - email: auth_user.email, - //avatar: auth_user.avatar, - servers, - token, - })).unwrap(); - } - }, - Err(_) => { - send_channel.send(Err(RPCError)).unwrap(); - }, - }; - }); - }, + fetch_user_data(send_channel, server_address, username, token); + } Err(e) => tracing::error!("Error in login flow: {:?}", e), } } @@ -174,19 +219,37 @@ impl eframe::App for TemplateApp { Ok(client_user) => { info!("Got data! User: {:?}", client_user); self.current_user = Some(client_user); - }, - Err(e) => error!("Error in login flow: {:?}", e), + } + Err(e) => error!("Error fetching data: {:?}", e), + } + } + + while let Ok(result) = self.added_server_channel.1.try_recv() { + match result { + Ok(address) => { + info!("New server added at: {:?}", address); + self.server_window_open = false; + + let send_channel = self.fetching_user_data_channel.0.clone(); + let server_address = self.saved_auth_address.clone().unwrap(); + let username = self.saved_username.clone().unwrap(); + let token = self.saved_token.clone().unwrap(); + fetch_user_data(send_channel, server_address, username, token); + } + Err(e) => error!("Error in adding a server: {:?}", e), } } // File -> Quit - panels::top_panel(self, ctx); - - panels::servers(self, ctx); - - panels::rooms(self, ctx); + gui::top_panel(self, ctx); - panels::messages(self, ctx) + gui::servers(self, ctx); + + gui::rooms(self, ctx); + + gui::messages(self, ctx); + + gui::modals(self, ctx) } /// Called by the frame work to save state before shutdown. diff --git a/client/src/main.rs b/client/src/main.rs index 1a696cc..8cf5274 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -28,6 +28,6 @@ async fn main() -> eframe::Result { eframe::run_native( "Realm", native_options, - Box::new(|cc| Ok(Box::new(realm_client::app::TemplateApp::new(cc)))), + Box::new(|cc| Ok(Box::new(realm_client::app::RealmApp::new(cc)))), ) } diff --git a/client/src/ui/panels.rs b/client/src/ui/gui.rs similarity index 72% rename from client/src/ui/panels.rs rename to client/src/ui/gui.rs index 07f4bdc..df1d464 100644 --- a/client/src/ui/panels.rs +++ b/client/src/ui/gui.rs @@ -5,9 +5,9 @@ use realm_auth::types::RealmAuthClient; use realm_shared::types::ErrorCode::RPCError; use regex::Regex; use tracing::log::*; -use crate::app::TemplateApp; +use crate::app::RealmApp; -pub fn top_panel(app: &mut TemplateApp, ctx: &Context) { +pub fn top_panel(app: &mut RealmApp, ctx: &Context) { egui::TopBottomPanel::top("top_panel").show(ctx, |ui| { egui::menu::bar(ui, |ui| { if app.current_user.is_none() && ui.button("Sign Up").clicked() { @@ -22,11 +22,11 @@ pub fn top_panel(app: &mut TemplateApp, ctx: &Context) { let address = app.current_user.clone().unwrap().server_address; let username = app.current_user.clone().unwrap().username; let token = app.current_user.clone().unwrap().token; - + let _handle = tokio::spawn(async move { let mut transport = tarpc::serde_transport::tcp::connect(&address, Json::default); transport.config_mut().max_frame_length(usize::MAX); - + let result = transport.await; let connection = match result { Ok(connection) => connection, @@ -35,29 +35,88 @@ pub fn top_panel(app: &mut TemplateApp, ctx: &Context) { return; } }; - + let client = RealmAuthClient::new(tarpc::client::Config::default(), connection).spawn(); let result = client.sign_out(context::current(), username, token).await; - + match result { Ok(_) => info!("Signed out!"), // TODO: properly handle this Err(e) => error!("Error signing out: {:?}", e), } }); - + app.current_user = None; + app.saved_username = None; + app.saved_token = None; + app.saved_auth_address = None; } if ui.button("Quit").clicked() { ctx.send_viewport_cmd(egui::ViewportCommand::Close); } - + ui.add_space(16.0); - egui::widgets::global_dark_light_mode_buttons(ui); + egui::widgets::global_theme_preference_buttons(ui); }); }); +} +pub fn servers(app: &mut RealmApp, ctx: &Context) { + egui::SidePanel::left("servers").show(ctx, |ui| { + ui.horizontal(|ui| { + ui.heading("Servers"); + if app.current_user.is_some() && ui.button("+").clicked() { + app.server_window_open = true; + } + }); + ui.separator(); + + if ui.add(SelectableLabel::new(app.selected, "server")).clicked() { + app.selected = !app.selected; + } + }); +} + +pub fn rooms(app: &mut RealmApp, ctx: &Context) { + egui::SidePanel::left("rooms").show(ctx, |ui| { + ui.heading("Rooms"); + ui.separator(); + + if ui.add(SelectableLabel::new(app.selected, "room")).clicked() { + app.selected = !app.selected; + } + }); +} + +pub fn messages(app: &mut RealmApp, ctx: &Context) { + egui::CentralPanel::default().show(ctx, |ui| { + // The central panel the region left after adding TopPanel's and SidePanel's + ui.heading("eframe template"); + + ui.horizontal(|ui| { + ui.label("Write something: "); + ui.text_edit_singleline(&mut app.label); + }); + + ui.add(egui::Slider::new(&mut app.value, 0.0..=10.0).text("value")); + if ui.button("Increment").clicked() { + app.value += 1.0; + } + + ui.separator(); + + ui.label(format!("Saved username: {:?}", app.saved_username)); + ui.label(format!("Saved token: {:?}", app.saved_token)); + ui.label(format!("Saved auth address: {:?}", app.saved_auth_address)); + + ui.separator(); + + ui.label(format!("Current user: {:?}", app.current_user)); + }); +} + +pub fn modals(app: &mut RealmApp, ctx: &Context) { egui::Window::new("Signup") .open(&mut app.signup_window_open) .min_size((500.0, 200.0)) @@ -201,47 +260,63 @@ pub fn top_panel(app: &mut TemplateApp, ctx: &Context) { //ui.close_menu() } }); -} + + egui::Window::new("Add Server") + .open(&mut app.server_window_open) + .min_size((500.0, 200.0)) + .show(ctx, |ui| { + ui.horizontal(|ui| { + ui.label("Domain: "); + ui.text_edit_singleline(&mut app.server_window_domain); + }); -pub fn servers(app: &mut TemplateApp, ctx: &Context) { - egui::SidePanel::left("servers").show(ctx, |ui| { - ui.heading("Servers"); - ui.separator(); + ui.horizontal(|ui| { + ui.label("Port: "); + if ui.text_edit_singleline(&mut app.server_window_port).changed() { + let re = Regex::new(r"[^0-9]+").unwrap(); + app.login_window_code = re.replace_all(&app.server_window_port, "").to_string(); + } + }); - if ui.add(SelectableLabel::new(app.selected, "server")).clicked() { - app.selected = !app.selected; - } - }); -} -pub fn rooms(app: &mut TemplateApp, ctx: &Context) { - egui::SidePanel::left("rooms").show(ctx, |ui| { - ui.heading("Rooms"); - ui.separator(); + if app.current_user.is_some() && ui.button("Add Server").clicked() { + let domain = app.server_window_domain.clone(); + let port = app.server_window_port.clone(); + let auth_address = app.current_user.clone().unwrap().server_address; + let auth_username = app.current_user.clone().unwrap().username; + let auth_token = app.current_user.clone().unwrap().token; + let send_channel = app.added_server_channel.0.clone(); - if ui.add(SelectableLabel::new(app.selected, "room")).clicked() { - app.selected = !app.selected; - } - }); -} + let _handle = tokio::spawn(async move { + let mut transport = tarpc::serde_transport::tcp::connect(auth_address, Json::default); + transport.config_mut().max_frame_length(usize::MAX); -pub fn messages(app: &mut TemplateApp, ctx: &Context) { - egui::CentralPanel::default().show(ctx, |ui| { - // The central panel the region left after adding TopPanel's and SidePanel's - ui.heading("eframe template"); + let result = transport.await; + let connection = match result { + Ok(connection) => connection, + Err(e) => { + tracing::error!("Failed to connect to server: {}", e); + return; + } + }; - ui.horizontal(|ui| { - ui.label("Write something: "); - ui.text_edit_singleline(&mut app.label); + let client = RealmAuthClient::new(tarpc::client::Config::default(), connection).spawn(); + let result = client.add_server( + context::current(), auth_username, auth_token, domain.clone(), port.parse::().unwrap()).await; + + match result { + Ok(r) => { + match r { + Ok(_) => { + info!("Server added successfully!"); + send_channel.send(Ok(format!("{}:{}", domain, port))).unwrap(); + }, + Err(e) => error!("Error adding server: {:?}", e), + } + }, + Err(_) => { send_channel.send(Err(RPCError)).unwrap(); }, + }; + }); + } }); - - ui.add(egui::Slider::new(&mut app.value, 0.0..=10.0).text("value")); - if ui.button("Increment").clicked() { - app.value += 1.0; - } - - ui.separator(); - - ui.label(format!("Current user: {:?}", app.current_user)); - }); } \ No newline at end of file diff --git a/client/src/ui/mod.rs b/client/src/ui/mod.rs index 25966b0..1d6bde4 100644 --- a/client/src/ui/mod.rs +++ b/client/src/ui/mod.rs @@ -1 +1 @@ -pub mod panels; \ No newline at end of file +pub mod gui; \ No newline at end of file