diff options
| -rw-r--r-- | README.md | 119 |
1 files changed, 104 insertions, 15 deletions
| @@ -16,6 +16,86 @@ On a high-level, this system should be: | |||
| 16 | `joyce` is structured as a small tool written in Rust running on a server, loosely inspired by [`twtxt`](https://github.com/buckket/twtxt). | 16 | `joyce` is structured as a small tool written in Rust running on a server, loosely inspired by [`twtxt`](https://github.com/buckket/twtxt). |
| 17 | Clients interface themselves with the server through a simple REST API. | 17 | Clients interface themselves with the server through a simple REST API. |
| 18 | 18 | ||
| 19 | |||
| 20 | # Installation and setup | ||
| 21 | |||
| 22 | <!-- TODO --> | ||
| 23 | |||
| 24 | ## Command Line Interface | ||
| 25 | |||
| 26 | `joyce` offers a very simple command line interface. | ||
| 27 | For an overview of the functionalities offered by the tool run | ||
| 28 | |||
| 29 | ```sh | ||
| 30 | joyce --help | ||
| 31 | ``` | ||
| 32 | |||
| 33 | `joyce` uses the `clap` library to parse command line arguments | ||
| 34 | |||
| 35 | ```{#dependencies .toml} | ||
| 36 | clap = { version = "3.1", features = ["derive"] } | ||
| 37 | ``` | ||
| 38 | |||
| 39 | ```{#main_uses .rust} | ||
| 40 | use clap::Parser; | ||
| 41 | ``` | ||
| 42 | |||
| 43 | using the [Derive API](https://github.com/clap-rs/clap/blob/v3.1.18/examples/tutorial_derive/README.md) to define the exposed functionalities. | ||
| 44 | The `struct` holding the CLI information is defined as follow | ||
| 45 | |||
| 46 | ```{#main_config .rust} | ||
| 47 | /// Record your thoughts as they come. | ||
| 48 | #[derive(Parser, Debug)] | ||
| 49 | #[clap(author, version, about, long_about = None)] | ||
| 50 | struct Config { | ||
| 51 | <<config_db>> | ||
| 52 | <<config_address>> | ||
| 53 | } | ||
| 54 | ``` | ||
| 55 | |||
| 56 | and the arguments are parsed as | ||
| 57 | |||
| 58 | ```{#config_parse .rust} | ||
| 59 | let config = Config::parse(); | ||
| 60 | ``` | ||
| 61 | |||
| 62 | `joyce` assumes a SQLite database named `notes.db` is present in the current working directory. | ||
| 63 | |||
| 64 | ```{#main_constants .rust} | ||
| 65 | const DB: &str = "./notes.db"; | ||
| 66 | ``` | ||
| 67 | |||
| 68 | A custom path to a SQLite database can be passed using the `-d/--database` flag. | ||
| 69 | |||
| 70 | ```{#main_uses .rust} | ||
| 71 | use std::path::PathBuf; | ||
| 72 | ``` | ||
| 73 | |||
| 74 | ```{#config_db .rust} | ||
| 75 | /// SQLite database [default: './notes.db'] | ||
| 76 | #[clap(short, long)] | ||
| 77 | database: Option<PathBuf>, | ||
| 78 | ``` | ||
| 79 | |||
| 80 | Once started, `joyce` will be available on localhost (`127.0.0.1`) at port `8080`. | ||
| 81 | |||
| 82 | ```{#main_constants .rust} | ||
| 83 | const ADDR: &str = "127.0.0.1"; | ||
| 84 | const PORT: u16 = 8080; | ||
| 85 | ``` | ||
| 86 | |||
| 87 | This behaviour can be overridden with the `-a`/`--address` and `-p`/`--port` flags | ||
| 88 | |||
| 89 | ```{#config_address .rust} | ||
| 90 | /// Address the `joyce` service is bound to [default: '127.0.0.1'] | ||
| 91 | #[clap(short, long)] | ||
| 92 | address: Option<String>, | ||
| 93 | /// Port the `joyce` service is bound to [default: 8080] | ||
| 94 | #[clap(short, long)] | ||
| 95 | port: Option<u16>, | ||
| 96 | ``` | ||
| 97 | |||
| 98 | |||
| 19 | # Notes | 99 | # Notes |
| 20 | 100 | ||
| 21 | Notes are the first-class citizen of `joyce` and are the main content exchanged between server and clients. | 101 | Notes are the first-class citizen of `joyce` and are the main content exchanged between server and clients. |
| @@ -243,6 +323,7 @@ rusqlite = { version = "0.28", features = ["chrono","serde_json"] } | |||
| 243 | The following code sets up a connection pool to the SQLite database. | 323 | The following code sets up a connection pool to the SQLite database. |
| 244 | 324 | ||
| 245 | ```{#db_uses .rust} | 325 | ```{#db_uses .rust} |
| 326 | use std::path::Path; | ||
| 246 | use r2d2_sqlite::SqliteConnectionManager; | 327 | use r2d2_sqlite::SqliteConnectionManager; |
| 247 | ``` | 328 | ``` |
| 248 | 329 | ||
| @@ -251,9 +332,9 @@ pub type Pool = r2d2::Pool<SqliteConnectionManager>; | |||
| 251 | ``` | 332 | ``` |
| 252 | 333 | ||
| 253 | ```{#db_connection_pool .rust} | 334 | ```{#db_connection_pool .rust} |
| 254 | pub fn get_connection_pool(db: &str) -> Pool { | 335 | pub fn get_connection_pool<P: AsRef<Path>>(db: P) -> Pool { |
| 255 | let manager = SqliteConnectionManager::file(db); | 336 | let manager = SqliteConnectionManager::file(db); |
| 256 | r2d2::Pool::new(manager).expect("Unable to connect to 'notes.db'") | 337 | r2d2::Pool::new(manager).expect("Unable to connect to {db}") |
| 257 | } | 338 | } |
| 258 | ``` | 339 | ``` |
| 259 | 340 | ||
| @@ -367,17 +448,21 @@ The `App` will register all request handlers defined above as *services*. | |||
| 367 | ```{#main_service .rust} | 448 | ```{#main_service .rust} |
| 368 | #[actix_web::main] | 449 | #[actix_web::main] |
| 369 | async fn main() -> std::io::Result<()> { | 450 | async fn main() -> std::io::Result<()> { |
| 370 | let db_pool = db::get_connection_pool("notes.db"); | 451 | <<config_parse>> |
| 371 | HttpServer::new(move || { | 452 | let addr = config.address.unwrap_or_else(|| ADDR.to_string()); |
| 372 | App::new() | 453 | let port = config.port.unwrap_or(PORT); |
| 373 | .app_data(web::Data::new(db_pool.clone())) | 454 | let db_file = config.database.unwrap_or_else(|| PathBuf::from(DB)); |
| 374 | .service(note::get_notes) | 455 | let db_pool = db::get_connection_pool(db_file); |
| 375 | .service(note::get_tags) | 456 | HttpServer::new(move || { |
| 376 | .service(note::post_notes) | 457 | App::new() |
| 377 | }) | 458 | .app_data(web::Data::new(db_pool.clone())) |
| 378 | .bind(("127.0.0.1", 8080))? | 459 | .service(note::get_notes) |
| 379 | .run() | 460 | .service(note::get_tags) |
| 380 | .await | 461 | .service(note::post_notes) |
| 462 | }) | ||
| 463 | .bind((addr, port))? | ||
| 464 | .run() | ||
| 465 | .await | ||
| 381 | } | 466 | } |
| 382 | ``` | 467 | ``` |
| 383 | 468 | ||
| @@ -391,6 +476,10 @@ mod db; | |||
| 391 | 476 | ||
| 392 | <<main_uses>> | 477 | <<main_uses>> |
| 393 | 478 | ||
| 479 | <<main_constants>> | ||
| 480 | |||
| 481 | <<main_config>> | ||
| 482 | |||
| 394 | <<main_service>> | 483 | <<main_service>> |
| 395 | ``` | 484 | ``` |
| 396 | 485 | ||
| @@ -429,11 +518,11 @@ Communication with SQLite is grouped under the `db` module. | |||
| 429 | # TODOs | 518 | # TODOs |
| 430 | 519 | ||
| 431 | - Better error handling | 520 | - Better error handling |
| 432 | - CLI with `clap` | 521 | - Logging |
| 522 | - add examples with cURL | ||
| 433 | 523 | ||
| 434 | ## Open questions | 524 | ## Open questions |
| 435 | 525 | ||
| 436 | - logging capabilities | ||
| 437 | - Should one be able to delete notes? Or mark them as read/processed? | 526 | - Should one be able to delete notes? Or mark them as read/processed? |
| 438 | - Authentication method? | 527 | - Authentication method? |
| 439 | - Custom filters on retrieval. | 528 | - Custom filters on retrieval. |
