oxedyne/fe2o3/fe2o3_steel/src/srv/ws/syntax.rs
18.2 KiB, 59 runs
created by r1870400018:1001, which is this file's identity for as long as the history lasts, whatever it is later renamed to
download · who wrote it · its history
| 1 | use oxedyne_fe2o3_core::prelude::*; |
| 2 | use oxedyne_fe2o3_jdat::{ |
| 3 | prelude::*, |
| 4 | version::SemVer, |
| 5 | }; |
| 6 | use oxedyne_fe2o3_syntax::{ |
| 7 | // arg::{ |
| 8 | // Arg, |
| 9 | // ArgConfig, |
| 10 | // }, |
| 11 | cmd::{ |
| 12 | Cmd, |
| 13 | CmdConfig, |
| 14 | }, |
| 15 | core::{ |
| 16 | Syntax, |
| 17 | SyntaxRef, |
| 18 | }, |
| 19 | }; |
| 20 | |
| 21 | #[derive(Clone, Debug)] |
| 22 | pub struct WebSocketSyntax; |
| 23 | |
| 24 | impl WebSocketSyntax { |
| 25 | |
| 26 | pub fn new( |
| 27 | name: &str, |
| 28 | ver: &SemVer, |
| 29 | about: &str, |
| 30 | ) |
| 31 | -> Outcome<SyntaxRef> |
| 32 | { |
| 33 | let mut s = Syntax::new(name).ver(*ver).about(about); |
| 34 | s = res!(s.with_default_help_cmd()); |
| 35 | |
| 36 | // ┌───────────────────────┐ |
| 37 | // │ CONTROL │ |
| 38 | // └───────────────────────┘ |
| 39 | |
| 40 | // --------------------------------------------------------------------------------------------- |
| 41 | // Command: exit |
| 42 | // --------------------------------------------------------------------------------------------- |
| 43 | let cmd = Cmd::from(CmdConfig { |
| 44 | name: fmt!("exit"), |
| 45 | help: Some(fmt!("Shutdown the app and exit, or use Ctrl+C, Ctrl+D")), |
| 46 | cat: fmt!("Control"), |
| 47 | ..Default::default() |
| 48 | }); |
| 49 | s = res!(s.add_cmd(cmd)); |
| 50 | // ============================================================================================= |
| 51 | |
| 52 | // ┌───────────────────────┐ |
| 53 | // │ DEVELOPMENT │ |
| 54 | // └───────────────────────┘ |
| 55 | |
| 56 | // --------------------------------------------------------------------------------------------- |
| 57 | // Command: dev_connect |
| 58 | // --------------------------------------------------------------------------------------------- |
| 59 | let cmd = Cmd::from(CmdConfig { |
| 60 | name: fmt!("dev_connect"), |
| 61 | help: Some(fmt!("Initialize dev mode refresh connection.")), |
| 62 | cat: fmt!("Development"), |
| 63 | ..Default::default() |
| 64 | }); |
| 65 | s = res!(s.add_cmd(cmd)); |
| 66 | // --------------------------------------------------------------------------------------------- |
| 67 | // Command: dev_ping |
| 68 | // --------------------------------------------------------------------------------------------- |
| 69 | let cmd = Cmd::from(CmdConfig { |
| 70 | name: fmt!("dev_ping"), |
| 71 | help: Some(fmt!("Keep dev mode connection alive.")), |
| 72 | cat: fmt!("Development"), |
| 73 | ..Default::default() |
| 74 | }); |
| 75 | s = res!(s.add_cmd(cmd)); |
| 76 | |
| 77 | // ┌───────────────────────┐ |
| 78 | // │ GENERAL IO │ |
| 79 | // └───────────────────────┘ |
| 80 | |
| 81 | // --------------------------------------------------------------------------------------------- |
| 82 | // Command: data |
| 83 | // --------------------------------------------------------------------------------------------- |
| 84 | let cmd = Cmd::from(CmdConfig { |
| 85 | name: fmt!("data"), |
| 86 | help: Some(fmt!("Data retrieved from database.")), |
| 87 | vals: vec![(Kind::Unknown, fmt!("Retrieved data")).into()], |
| 88 | cat: fmt!("Database IO"), |
| 89 | ..Default::default() |
| 90 | }); |
| 91 | s = res!(s.add_cmd(cmd)); |
| 92 | // --------------------------------------------------------------------------------------------- |
| 93 | // Command: info |
| 94 | // --------------------------------------------------------------------------------------------- |
| 95 | let cmd = Cmd::from(CmdConfig { |
| 96 | name: fmt!("info"), |
| 97 | help: Some(fmt!("For your information.")), |
| 98 | vals: vec![(Kind::Str, fmt!("Information message")).into()], |
| 99 | cat: fmt!("General IO"), |
| 100 | ..Default::default() |
| 101 | }); |
| 102 | s = res!(s.add_cmd(cmd)); |
| 103 | // --------------------------------------------------------------------------------------------- |
| 104 | // Command: error |
| 105 | // --------------------------------------------------------------------------------------------- |
| 106 | let cmd = Cmd::from(CmdConfig { |
| 107 | name: fmt!("error"), |
| 108 | help: Some(fmt!("Error.")), |
| 109 | vals: vec![(Kind::Str, fmt!("Error message")).into()], |
| 110 | cat: fmt!("General IO"), |
| 111 | ..Default::default() |
| 112 | }); |
| 113 | s = res!(s.add_cmd(cmd)); |
| 114 | // --------------------------------------------------------------------------------------------- |
| 115 | // Command: echo |
| 116 | // --------------------------------------------------------------------------------------------- |
| 117 | let cmd = Cmd::from(CmdConfig { |
| 118 | name: fmt!("echo"), |
| 119 | help: Some(fmt!("Echo the incoming message.")), |
| 120 | vals: vec![(Kind::Str, fmt!("Text to echo")).into()], |
| 121 | cat: fmt!("General IO"), |
| 122 | ..Default::default() |
| 123 | }); |
| 124 | s = res!(s.add_cmd(cmd)); |
| 125 | // ============================================================================================= |
| 126 | |
| 127 | // ┌───────────────────────┐ |
| 128 | // │ DATABASE IO │ |
| 129 | // └───────────────────────┘ |
| 130 | |
| 131 | // --------------------------------------------------------------------------------------------- |
| 132 | // Command: insert |
| 133 | // --------------------------------------------------------------------------------------------- |
| 134 | let cmd = Cmd::from(CmdConfig { |
| 135 | name: fmt!("insert"), |
| 136 | help: Some(fmt!("Insert a key-value pair into the database.")), |
| 137 | vals: vec![ |
| 138 | (Kind::Unknown, fmt!("Key")).into(), |
| 139 | (Kind::Unknown, fmt!("Value")).into(), |
| 140 | ], |
| 141 | cat: fmt!("Database IO"), |
| 142 | ..Default::default() |
| 143 | }); |
| 144 | s = res!(s.add_cmd(cmd)); |
| 145 | // ============================================================================================= |
| 146 | |
| 147 | // --------------------------------------------------------------------------------------------- |
| 148 | // Command: get_data |
| 149 | // --------------------------------------------------------------------------------------------- |
| 150 | let cmd = Cmd::from(CmdConfig { |
| 151 | name: fmt!("get_data"), |
| 152 | help: Some(fmt!("Get database value for given key daticle.")), |
| 153 | vals: vec![(Kind::Unknown, fmt!("Key")).into()], |
| 154 | cat: fmt!("Database IO"), |
| 155 | ..Default::default() |
| 156 | }); |
| 157 | s = res!(s.add_cmd(cmd)); |
| 158 | // ============================================================================================= |
| 159 | |
| 160 | // ┌───────────────────────┐ |
| 161 | // │ SESSION IO │ |
| 162 | // └───────────────────────┘ |
| 163 | |
| 164 | // --------------------------------------------------------------------------------------------- |
| 165 | // Command: sess_get |
| 166 | // --------------------------------------------------------------------------------------------- |
| 167 | // Reads a value from the caller's session-scoped keyspace. The |
| 168 | // server automatically prefixes the caller's session id (taken from |
| 169 | // the HttpOnly session cookie attached at the WebSocket upgrade), |
| 170 | // so the client cannot cross into another user's namespace. |
| 171 | let cmd = Cmd::from(CmdConfig { |
| 172 | name: fmt!("sess_get"), |
| 173 | help: Some(fmt!("Read a value from the caller's session-scoped storage.")), |
| 174 | vals: vec![(Kind::Str, fmt!("Key (string)")).into()], |
| 175 | cat: fmt!("Session IO"), |
| 176 | ..Default::default() |
| 177 | }); |
| 178 | s = res!(s.add_cmd(cmd)); |
| 179 | |
| 180 | // --------------------------------------------------------------------------------------------- |
| 181 | // Command: sess_put |
| 182 | // --------------------------------------------------------------------------------------------- |
| 183 | // Writes a (key, value) pair into the caller's session-scoped |
| 184 | // keyspace. The key must be a string; the value can be any Dat. |
| 185 | let cmd = Cmd::from(CmdConfig { |
| 186 | name: fmt!("sess_put"), |
| 187 | help: Some(fmt!("Write a (key, value) pair into session-scoped storage.")), |
| 188 | vals: vec![ |
| 189 | (Kind::Str, fmt!("Key")).into(), |
| 190 | (Kind::Unknown, fmt!("Value")).into(), |
| 191 | ], |
| 192 | cat: fmt!("Session IO"), |
| 193 | ..Default::default() |
| 194 | }); |
| 195 | s = res!(s.add_cmd(cmd)); |
| 196 | // ============================================================================================= |
| 197 | |
| 198 | // ┌───────────────────────┐ |
| 199 | // │ USER IO │ |
| 200 | // └───────────────────────┘ |
| 201 | |
| 202 | // --------------------------------------------------------------------------------------------- |
| 203 | // Command: user_get |
| 204 | // --------------------------------------------------------------------------------------------- |
| 205 | // Reads a value from the authenticated user's keyspace. The server |
| 206 | // looks up the session's bound user via `sess_meta:<sid>` and |
| 207 | // prefixes the request with `user:<username>:`. Rejects when the |
| 208 | // session is not authenticated. |
| 209 | let cmd = Cmd::from(CmdConfig { |
| 210 | name: fmt!("user_get"), |
| 211 | help: Some(fmt!("Read a value from the authenticated user's storage.")), |
| 212 | vals: vec![(Kind::Str, fmt!("Key (string)")).into()], |
| 213 | cat: fmt!("User IO"), |
| 214 | ..Default::default() |
| 215 | }); |
| 216 | s = res!(s.add_cmd(cmd)); |
| 217 | |
| 218 | // --------------------------------------------------------------------------------------------- |
| 219 | // Command: user_put |
| 220 | // --------------------------------------------------------------------------------------------- |
| 221 | // Writes a (key, value) pair into the authenticated user's |
| 222 | // keyspace. The server scopes the key by the session-bound user |
| 223 | // and rejects if the session is not authenticated. |
| 224 | let cmd = Cmd::from(CmdConfig { |
| 225 | name: fmt!("user_put"), |
| 226 | help: Some(fmt!("Write a (key, value) pair into user-scoped storage.")), |
| 227 | vals: vec![ |
| 228 | (Kind::Str, fmt!("Key")).into(), |
| 229 | (Kind::Unknown, fmt!("Value")).into(), |
| 230 | ], |
| 231 | cat: fmt!("User IO"), |
| 232 | ..Default::default() |
| 233 | }); |
| 234 | s = res!(s.add_cmd(cmd)); |
| 235 | // ============================================================================================= |
| 236 | |
| 237 | // ┌───────────────────────┐ |
| 238 | // │ AUTH │ |
| 239 | // └───────────────────────┘ |
| 240 | |
| 241 | // --------------------------------------------------------------------------------------------- |
| 242 | // Command: register |
| 243 | // --------------------------------------------------------------------------------------------- |
| 244 | // Create a new user record. The passphrase is hashed with Argon2id |
| 245 | // before storage; the plain passphrase is never written to disk. |
| 246 | let cmd = Cmd::from(CmdConfig { |
| 247 | name: fmt!("register"), |
| 248 | help: Some(fmt!("Create a new user record keyed by username.")), |
| 249 | vals: vec![ |
| 250 | (Kind::Str, fmt!("Username")).into(), |
| 251 | (Kind::Str, fmt!("Passphrase")).into(), |
| 252 | ], |
| 253 | cat: fmt!("Auth"), |
| 254 | ..Default::default() |
| 255 | }); |
| 256 | s = res!(s.add_cmd(cmd)); |
| 257 | |
| 258 | // --------------------------------------------------------------------------------------------- |
| 259 | // Command: login |
| 260 | // --------------------------------------------------------------------------------------------- |
| 261 | // Verify credentials and bind the current session to a user. A |
| 262 | // successful login writes a `sess_meta:<sid>` record recording |
| 263 | // which user the session is now acting as. |
| 264 | let cmd = Cmd::from(CmdConfig { |
| 265 | name: fmt!("login"), |
| 266 | help: Some(fmt!("Verify credentials and bind the current session to the user.")), |
| 267 | vals: vec![ |
| 268 | (Kind::Str, fmt!("Username")).into(), |
| 269 | (Kind::Str, fmt!("Passphrase")).into(), |
| 270 | ], |
| 271 | cat: fmt!("Auth"), |
| 272 | ..Default::default() |
| 273 | }); |
| 274 | s = res!(s.add_cmd(cmd)); |
| 275 | |
| 276 | // --------------------------------------------------------------------------------------------- |
| 277 | // Command: logout |
| 278 | // --------------------------------------------------------------------------------------------- |
| 279 | // Clear the session-to-user binding for the caller's session. The |
| 280 | // underlying HttpOnly cookie is not touched; the session remains |
| 281 | // anonymous until the next successful login. |
| 282 | let cmd = Cmd::from(CmdConfig { |
| 283 | name: fmt!("logout"), |
| 284 | help: Some(fmt!("Clear the session-to-user binding for this session.")), |
| 285 | cat: fmt!("Auth"), |
| 286 | ..Default::default() |
| 287 | }); |
| 288 | s = res!(s.add_cmd(cmd)); |
| 289 | |
| 290 | // --------------------------------------------------------------------------------------------- |
| 291 | // Command: whoami |
| 292 | // --------------------------------------------------------------------------------------------- |
| 293 | // Report whether the caller's session is authenticated and, if so, |
| 294 | // under which username. The session id itself is never returned. |
| 295 | let cmd = Cmd::from(CmdConfig { |
| 296 | name: fmt!("whoami"), |
| 297 | help: Some(fmt!("Report the authenticated user for this session, if any.")), |
| 298 | cat: fmt!("Auth"), |
| 299 | ..Default::default() |
| 300 | }); |
| 301 | s = res!(s.add_cmd(cmd)); |
| 302 | // ============================================================================================= |
| 303 | |
| 304 | // ┌───────────────────────┐ |
| 305 | // │ TERMINAL │ |
| 306 | // └───────────────────────┘ |
| 307 | |
| 308 | // --------------------------------------------------------------------------------------------- |
| 309 | // Command: term_new |
| 310 | // --------------------------------------------------------------------------------------------- |
| 311 | // Create a new terminal session (tmux). Returns the session |
| 312 | // name in the data response. |
| 313 | let cmd = Cmd::from(CmdConfig { |
| 314 | name: fmt!("term_new"), |
| 315 | help: Some(fmt!("Create a new terminal session.")), |
| 316 | cat: fmt!("Terminal"), |
| 317 | ..Default::default() |
| 318 | }); |
| 319 | s = res!(s.add_cmd(cmd)); |
| 320 | // ============================================================================================= |
| 321 | |
| 322 | // --------------------------------------------------------------------------------------------- |
| 323 | // Command: term_list |
| 324 | // --------------------------------------------------------------------------------------------- |
| 325 | // List active terminal sessions. Returns a map with a |
| 326 | // "sessions" array of { "name": "goose-N" } entries. |
| 327 | let cmd = Cmd::from(CmdConfig { |
| 328 | name: fmt!("term_list"), |
| 329 | help: Some(fmt!("List active terminal sessions.")), |
| 330 | cat: fmt!("Terminal"), |
| 331 | ..Default::default() |
| 332 | }); |
| 333 | s = res!(s.add_cmd(cmd)); |
| 334 | // ============================================================================================= |
| 335 | |
| 336 | // --------------------------------------------------------------------------------------------- |
| 337 | // Command: term_close |
| 338 | // --------------------------------------------------------------------------------------------- |
| 339 | // Kill a terminal session by name. |
| 340 | let cmd = Cmd::from(CmdConfig { |
| 341 | name: fmt!("term_close"), |
| 342 | help: Some(fmt!("Kill a terminal session by name.")), |
| 343 | vals: vec![(Kind::Str, fmt!("Session name")).into()], |
| 344 | cat: fmt!("Terminal"), |
| 345 | ..Default::default() |
| 346 | }); |
| 347 | s = res!(s.add_cmd(cmd)); |
| 348 | // ============================================================================================= |
| 349 | |
| 350 | // --------------------------------------------------------------------------------------------- |
| 351 | // Command: term_set_name |
| 352 | // --------------------------------------------------------------------------------------------- |
| 353 | // Rename a terminal session. |
| 354 | let cmd = Cmd::from(CmdConfig { |
| 355 | name: fmt!("term_set_name"), |
| 356 | help: Some(fmt!("Rename a terminal session.")), |
| 357 | vals: vec![ |
| 358 | (Kind::Str, fmt!("Old session name")).into(), |
| 359 | (Kind::Str, fmt!("New session name")).into(), |
| 360 | ], |
| 361 | cat: fmt!("Terminal"), |
| 362 | ..Default::default() |
| 363 | }); |
| 364 | s = res!(s.add_cmd(cmd)); |
| 365 | // ============================================================================================= |
| 366 | |
| 367 | // --------------------------------------------------------------------------------------------- |
| 368 | // Command: change_pass |
| 369 | // --------------------------------------------------------------------------------------------- |
| 370 | // Change the authenticated user's passphrase. Requires the |
| 371 | // old passphrase for verification and the new passphrase |
| 372 | // (entered twice on the client side). |
| 373 | let cmd = Cmd::from(CmdConfig { |
| 374 | name: fmt!("change_pass"), |
| 375 | help: Some(fmt!("Change the authenticated user's passphrase.")), |
| 376 | vals: vec![ |
| 377 | (Kind::Str, fmt!("Old passphrase")).into(), |
| 378 | (Kind::Str, fmt!("New passphrase")).into(), |
| 379 | ], |
| 380 | cat: fmt!("Auth"), |
| 381 | ..Default::default() |
| 382 | }); |
| 383 | s = res!(s.add_cmd(cmd)); |
| 384 | // ============================================================================================= |
| 385 | |
| 386 | Ok(SyntaxRef::new(s)) |
| 387 | } |
| 388 | } |