diff options
Diffstat (limited to 'README.md')
| -rw-r--r-- | README.md | 196 |
1 files changed, 196 insertions, 0 deletions
diff --git a/README.md b/README.md new file mode 100644 index 0000000..1e36c2d --- /dev/null +++ b/README.md | |||
| @@ -0,0 +1,196 @@ | |||
| 1 | --- | ||
| 2 | title: joyce - record your thoughts as they come | ||
| 3 | author: Federico Igne | ||
| 4 | date: \today | ||
| 5 | --- | ||
| 6 | |||
| 7 | `joyce` is an attempt at building a tool for rapid notetaking, i.e., quick collection of short thoughts that can come to mind at any point. | ||
| 8 | |||
| 9 | On a high-level, this system should be: | ||
| 10 | |||
| 11 | - *Hubiquitous*, needs to be available (read/write) whenever one has internet connection (falling back to pen&paper, otherwise). | ||
| 12 | - *Searchable*, one has to be able to search and filter the pile of notes. | ||
| 13 | - *Out of the way*, sophistication will only get in the way of recording one's thoughts. | ||
| 14 | |||
| 15 | `joyce` is structured as a small tool written in Rust running on a server, loosely inspired by [`twtxt`](https://github.com/buckket/twtxt). | ||
| 16 | Clients interface themselves with the server through a simple REST API. | ||
| 17 | |||
| 18 | # Notes | ||
| 19 | |||
| 20 | Notes are the first-class citizen of `joyce` and are the main content exchanged between server and clients. | ||
| 21 | |||
| 22 | Notes, along with their REST API are defined in their own module | ||
| 23 | |||
| 24 | ```{#main_mods .rust} | ||
| 25 | mod note; | ||
| 26 | ``` | ||
| 27 | |||
| 28 | ```{#note.rs .rust path="src/"} | ||
| 29 | <<note_uses>> | ||
| 30 | |||
| 31 | <<note_struct>> | ||
| 32 | |||
| 33 | <<note_impl>> | ||
| 34 | |||
| 35 | <<req_get_notes>> | ||
| 36 | ``` | ||
| 37 | |||
| 38 | ## Anatomy of a note | ||
| 39 | |||
| 40 | A *note* is a simple structure recording a timestamp determined at creation, a list of tags, and the body of the note. | ||
| 41 | |||
| 42 | ```{#note_struct .rust} | ||
| 43 | #[derive(Debug, Deserialize, Serialize)] | ||
| 44 | pub struct Note { | ||
| 45 | timestamp: DateTime<Utc>, | ||
| 46 | tags: Vec<String>, | ||
| 47 | body: String, | ||
| 48 | } | ||
| 49 | |||
| 50 | <<note_collection>> | ||
| 51 | ``` | ||
| 52 | |||
| 53 | ```{#note_impl .rust} | ||
| 54 | impl Note { | ||
| 55 | fn new(body: String, tags: Vec<String>) -> Self { | ||
| 56 | Self { timestamp: Utc::now(), tags, body } | ||
| 57 | } | ||
| 58 | } | ||
| 59 | ``` | ||
| 60 | |||
| 61 | A set of notes is just a `Vec` of `Note`s. | ||
| 62 | |||
| 63 | ```{#note_collection .rust} | ||
| 64 | type Notes = Vec<Note>; | ||
| 65 | ``` | ||
| 66 | |||
| 67 | ### (De)serialization | ||
| 68 | |||
| 69 | Since notes need to be sent and received via HTTP, the structure needs to be *serializable*. | ||
| 70 | |||
| 71 | ```{#dependencies .toml} | ||
| 72 | serde = { version = "1.0", features = ["derive"] } | ||
| 73 | ``` | ||
| 74 | |||
| 75 | ```{#note_uses .rust} | ||
| 76 | use serde::{Serialize, Deserialize}; | ||
| 77 | ``` | ||
| 78 | |||
| 79 | ### Timestamps | ||
| 80 | |||
| 81 | Timestamps adhere the *RFC 3339 date-time standard* with UTC offset. | ||
| 82 | |||
| 83 | ```{#dependencies .toml} | ||
| 84 | chrono = { version = "0.4", features = ["serde"] } | ||
| 85 | ``` | ||
| 86 | |||
| 87 | ```{#note_uses .rust} | ||
| 88 | use chrono::prelude::{DateTime, Utc}; | ||
| 89 | ``` | ||
| 90 | |||
| 91 | # The REST API | ||
| 92 | |||
| 93 | `joyce` uses [`actix-web`](https://actix.rs/) to handle HTTP requests and responses. | ||
| 94 | |||
| 95 | ```{#dependencies .toml} | ||
| 96 | actix-web = "4.1" | ||
| 97 | ``` | ||
| 98 | |||
| 99 | ## Resources | ||
| 100 | |||
| 101 | - [Tutorial](https://web.archive.org/web/20220710213947/https://hub.qovery.com/guides/tutorial/create-a-blazingly-fast-api-in-rust-part-1/) | ||
| 102 | - [Introduction to `actix`](https://actix.rs/docs/getting-started/) | ||
| 103 | |||
| 104 | |||
| 105 | ## GET /notes | ||
| 106 | |||
| 107 | Each request handlers is an *async* function that accepts zero or more parameters, extracted from a request (see [`FromRequest`](https://docs.rs/actix-web/latest/actix_web/trait.FromRequest.html) trait), and returns an [`HttpResponse`](https://docs.rs/actix-web/latest/actix_web/struct.HttpResponse.html). | ||
| 108 | |||
| 109 | Here we are requesting the list of notes currently in the system. | ||
| 110 | The function takes 0 parameters and returns a JSON object. | ||
| 111 | |||
| 112 | ```{#note_uses .rust} | ||
| 113 | use actix_web::{HttpResponse,get}; | ||
| 114 | use actix_web::http::header::ContentType; | ||
| 115 | ``` | ||
| 116 | |||
| 117 | ```{#req_get_notes .rust} | ||
| 118 | #[get("/notes")] | ||
| 119 | pub async fn list() -> HttpResponse { | ||
| 120 | let mut notes: Notes = vec![]; | ||
| 121 | |||
| 122 | <<notes_retrieve>> | ||
| 123 | |||
| 124 | HttpResponse::Ok() | ||
| 125 | .content_type(ContentType::json()) | ||
| 126 | .json(notes) | ||
| 127 | } | ||
| 128 | ``` | ||
| 129 | |||
| 130 | # The program | ||
| 131 | |||
| 132 | The main program is structured as follows | ||
| 133 | |||
| 134 | ```{#main.rs .rust path="src/"} | ||
| 135 | <<main_uses>> | ||
| 136 | |||
| 137 | <<main_mods>> | ||
| 138 | |||
| 139 | <<main_service>> | ||
| 140 | ``` | ||
| 141 | |||
| 142 | # Main service | ||
| 143 | |||
| 144 | The main service will instantiate a new `App` running within a `HttpServer` bound to *localhost* on port 8080. | ||
| 145 | |||
| 146 | ```{#main_uses .rust} | ||
| 147 | use actix_web::{App, HttpServer}; | ||
| 148 | ``` | ||
| 149 | |||
| 150 | The `App` will register all request handlers defined above. | ||
| 151 | |||
| 152 | ```{#main_service .rust} | ||
| 153 | #[actix_web::main] | ||
| 154 | async fn main() -> std::io::Result<()> { | ||
| 155 | HttpServer::new(|| { | ||
| 156 | App::new() | ||
| 157 | .service(note::list) | ||
| 158 | }) | ||
| 159 | .bind(("127.0.0.1", 8080))? | ||
| 160 | .run() | ||
| 161 | .await | ||
| 162 | } | ||
| 163 | ``` | ||
| 164 | |||
| 165 | # Testing | ||
| 166 | |||
| 167 | ```{#notes_retrieve .rust} | ||
| 168 | notes.push(Note::new( | ||
| 169 | String::from("This is a first test note!"), | ||
| 170 | vec![String::from("test"), String::from("alpha"), String::from("literate")])); | ||
| 171 | notes.push(Note::new( | ||
| 172 | String::from("This is my favourite emoji: 🦥"), | ||
| 173 | vec![String::from("test"), String::from("emoji")])); | ||
| 174 | notes.push(Note::new( | ||
| 175 | String::from("Here is a link: https://www.federicoigne.com/"), | ||
| 176 | vec![String::from("test"), String::from("links")])); | ||
| 177 | ``` | ||
| 178 | # Open questions | ||
| 179 | |||
| 180 | - Should one be able to delete notes? Or mark them as read/processed? | ||
| 181 | - Authentication method? | ||
| 182 | - Custom filters on retrieval. | ||
| 183 | |||
| 184 | # Credits | ||
| 185 | |||
| 186 | `joyce v0.1.0` was created by Federico Igne ([git@federicoigne.com](mailto:git@federicoigne.com)) and available at [`https://git.dyamon.me/projects/joyce`](https://git.dyamon.me/projects/joyce). | ||
| 187 | |||
| 188 | ```{#Cargo.toml .toml} | ||
| 189 | [package] | ||
| 190 | name = "joyce" | ||
| 191 | version = "0.1.0" | ||
| 192 | edition = "2021" | ||
| 193 | |||
| 194 | [dependencies] | ||
| 195 | <<dependencies>> | ||
| 196 | ``` | ||
