From 9478e6a91ad8bd49f977d05e18eac9930a9556b6 Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Wed, 4 Mar 2026 17:55:53 +0100 Subject: [PATCH] Enhance README with CLI room checking instructions and implement room name resolution in command handling. Updated `handle_init_command` and `handle_join_command` to use resolved room names, improving room access validation. Added CLI command handling in `main.rs` to list available rooms from the database or fallback configuration. --- README.md | 18 ++++++++++++++ src/commands.rs | 64 +++++++++++++++++++++++++++++++------------------ src/main.rs | 45 ++++++++++++++++++++++++++++++++++ src/types.rs | 1 + 4 files changed, 105 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index f2bbbac..c7fdcc3 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,24 @@ In `YourPart3/backend/config/chatBridge.json` sollte stehen: Dann verbindet sich die bestehende Bridge (`chatTcpBridge.js`) direkt mit `yourchat2`. +## Raeume per CLI pruefen + +Die Raumdefinitionen kommen aus `chat.room` (bei gesetztem `CHAT_DB_URL`). +Ohne DB-Verbindung gibt es einen Fallback mit `lobby`. + +Pruefen auf Kommandozeile: + +```bash +cd /home/tsschulz/Programs/yourchat2 +CHAT_DB_URL='postgres://user:pass@host:5432/dbname' ./target/release/yourchat2 --list-rooms +``` + +Wenn der Service ueber systemd laeuft und die Variablen in `/etc/yourchat2/yourchat2.env` stehen: + +```bash +sudo -u tsschulz bash -lc 'set -a; source /etc/yourchat2/yourchat2.env; set +a; /usr/local/bin/yourchat2 --list-rooms' +``` + ## Systemd (optional) Es liegt eine Produktions-nahe Unit-Datei `yourchat2.service` im Projekt. diff --git a/src/commands.rs b/src/commands.rs index 1abb83b..776e484 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -148,7 +148,7 @@ async fn handle_init_command( let room_name = normalize_room_name(command.room.as_deref().unwrap_or("lobby")); let room_password = command.password.clone().unwrap_or_default(); - let (token, user_name) = { + let (token, user_name, actual_room_name) = { let mut guard = state.write().await; if guard.logged_in_names.contains(&requested_user_name) && guard @@ -160,8 +160,13 @@ async fn handle_init_command( send_error(client_id, Arc::clone(&state), "loggedin").await; return; } + let Some(resolved_room_name) = resolve_room_name(&guard, &room_name) else { + drop(guard); + send_error(client_id, Arc::clone(&state), "room_not_found_or_join_failed").await; + return; + }; if !room_access_allowed( - guard.room_meta.get(&room_name), + guard.room_meta.get(&resolved_room_name), &profile.display_name, profile.falukant_user_id, profile.age, @@ -187,7 +192,7 @@ async fn handle_init_command( client.age = profile.age; client.rights = profile.rights.clone(); client.logged_in = true; - client.room = room_name.clone(); + client.room = resolved_room_name.clone(); let mut new_token = None; if client.token.is_none() { @@ -218,8 +223,12 @@ async fn handle_init_command( members.remove(&client_id); } } - guard.rooms.entry(room_name.clone()).or_default().insert(client_id); - (token, user_name) + guard + .rooms + .entry(resolved_room_name.clone()) + .or_default() + .insert(client_id); + (token, user_name, resolved_room_name) }; state::send_to_client( @@ -232,13 +241,13 @@ async fn handle_init_command( state::send_to_client( client_id, Arc::clone(&state), - json!({"type":5, "message":"room_entered", "to": room_name}), + json!({"type":5, "message":"room_entered", "to": actual_room_name}), ) .await; state::broadcast_room( - &room_name, + &actual_room_name, Arc::clone(&state), - json!({"type":"system","message": format!("{user_name} joined {room_name}")}), + json!({"type":"system","message": format!("{user_name} joined {actual_room_name}")}), Some(client_id), ) .await; @@ -255,8 +264,13 @@ async fn handle_join_command( } let room = normalize_room_name(command.room.as_deref().or(command.name.as_deref()).unwrap_or("lobby")); let password = command.password.clone().unwrap_or_default(); - let (from_room, user_name) = { + let (from_room, user_name, actual_room_name) = { let mut guard = state.write().await; + let Some(resolved_room) = resolve_room_name(&guard, &room) else { + drop(guard); + send_error(client_id, Arc::clone(&state), "room_not_found_or_join_failed").await; + return; + }; let (name_for_check, falukant_user_id, age) = { let Some(client) = guard.clients.get(&client_id) else { return; @@ -264,7 +278,7 @@ async fn handle_join_command( (client.user_name.clone(), client.falukant_user_id, client.age) }; if !room_access_allowed( - guard.room_meta.get(&room), + guard.room_meta.get(&resolved_room), &name_for_check, falukant_user_id, age, @@ -279,14 +293,14 @@ async fn handle_join_command( }; let from_room = client.room.clone(); let user_name = client.user_name.clone(); - client.room = room.clone(); + client.room = resolved_room.clone(); if !from_room.is_empty() { if let Some(members) = guard.rooms.get_mut(&from_room) { members.remove(&client_id); } } - guard.rooms.entry(room.clone()).or_default().insert(client_id); - (from_room, user_name) + guard.rooms.entry(resolved_room.clone()).or_default().insert(client_id); + (from_room, user_name, resolved_room) }; if !from_room.is_empty() && from_room != room { @@ -301,7 +315,7 @@ async fn handle_join_command( state::send_to_client( client_id, Arc::clone(&state), - json!({"type":5, "message":"room_entered", "to": room}), + json!({"type":5, "message":"room_entered", "to": actual_room_name}), ) .await; state::send_room_list(client_id, Arc::clone(&state)).await; @@ -754,7 +768,7 @@ async fn handle_unknown_command(client_id: ClientId, command: &Command, state: A fn room_access_allowed( room_meta: Option<&RoomMeta>, - user_name: &str, + _user_name: &str, falukant_user_id: Option, age: Option, provided_password: &str, @@ -792,17 +806,21 @@ fn room_access_allowed( } } - if !room.is_public { - if room.owner_id.is_none() { - return false; - } - if user_name.trim().is_empty() { - return false; - } - } true } +fn resolve_room_name(state: &ChatState, requested: &str) -> Option { + if state.room_meta.contains_key(requested) { + return Some(requested.to_string()); + } + let requested_lower = requested.to_lowercase(); + state + .room_meta + .keys() + .find(|name| name.to_lowercase() == requested_lower) + .cloned() +} + fn normalize_room_name(input: &str) -> String { let trimmed = input.trim(); if trimmed.is_empty() { diff --git a/src/main.rs b/src/main.rs index 118edab..8ebfcad 100644 --- a/src/main.rs +++ b/src/main.rs @@ -38,6 +38,10 @@ async fn main() -> Result<(), Box> { allowed_users: db::parse_allowed_users(), db_client, }); + if handle_cli_commands().as_deref() == Some("--list-rooms") { + print_rooms_for_cli(&config).await?; + return Ok(()); + } let rooms = db::load_room_configs(&config).await.unwrap_or_else(|_| { vec![types::RoomMeta { name: "lobby".to_string(), @@ -212,6 +216,47 @@ async fn main() -> Result<(), Box> { Ok(()) } +fn handle_cli_commands() -> Option { + env::args().nth(1) +} + +async fn print_rooms_for_cli( + config: &ServerConfig, +) -> Result<(), Box> { + let rooms = db::load_room_configs(config) + .await + .map_err(std::io::Error::other)?; + + println!( + "yourchat2 rooms source: {}", + if config.db_client.is_some() { "database" } else { "fallback" } + ); + println!( + "{:<30} {:<8} {:<8} {:<8} {:<10}", + "name", "public", "min_age", "max_age", "password" + ); + println!("{}", "-".repeat(72)); + for room in rooms { + println!( + "{:<30} {:<8} {:<8} {:<8} {:<10}", + room.name, + if room.is_public { "yes" } else { "no" }, + room.min_age + .map(|v| v.to_string()) + .unwrap_or_else(|| "-".to_string()), + room.max_age + .map(|v| v.to_string()) + .unwrap_or_else(|| "-".to_string()), + if room.password.as_deref().unwrap_or("").is_empty() { + "none" + } else { + "set" + }, + ); + } + Ok(()) +} + async fn handle_client( stream: S, state: Arc>, diff --git a/src/types.rs b/src/types.rs index e473aed..66b8147 100644 --- a/src/types.rs +++ b/src/types.rs @@ -78,6 +78,7 @@ pub(crate) struct DiceGame { pub(crate) total_scores: HashMap, } +#[allow(dead_code)] #[derive(Clone, Debug, Default)] pub(crate) struct RoomMeta { pub(crate) name: String,