From 2ec3e5b74b61233f28de99ef36c0bed156864f32 Mon Sep 17 00:00:00 2001 From: Federico Igne Date: Wed, 27 Jul 2022 23:43:46 +0100 Subject: feat(POST /notes): add ability to add new notes. Notes are stored in a simple json file for now. Will implement a proper backend later on (most likely SQLite). --- README.md | 117 +++++++++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 96 insertions(+), 21 deletions(-) (limited to 'README.md') diff --git a/README.md b/README.md index 1e36c2d..0f6df09 100644 --- a/README.md +++ b/README.md @@ -32,36 +32,57 @@ mod note; <> +<> + <> + +<> ``` ## Anatomy of a note -A *note* is a simple structure recording a timestamp determined at creation, a list of tags, and the body of the note. +A *note* is a simple structure recording a unique UUID and a timestamp, both assigned at creation, a list of tags, and the body of the note. +A set of notes is just a `Vec` of `Note`s. ```{#note_struct .rust} #[derive(Debug, Deserialize, Serialize)] pub struct Note { + uuid: Uuid, timestamp: DateTime, tags: Vec, body: String, } -<> +type Notes = Vec; ``` +A `Note` can be automatically created from a `NoteRequest`. + ```{#note_impl .rust} impl Note { fn new(body: String, tags: Vec) -> Self { - Self { timestamp: Utc::now(), tags, body } + Self { uuid: Uuid::new_v4(), timestamp: Utc::now(), tags, body } } } + +impl From for Note { + fn from(req: NoteRequest) -> Self { + Self::new(req.body, req.tags) + } +} ``` -A set of notes is just a `Vec` of `Note`s. +Similarly, a `NoteRequest` is what a client would request at note creation. +It contains the same information as a `Note` without the information assigned at creation by the server. -```{#note_collection .rust} -type Notes = Vec; +```{#note_request_struct .rust} +#[derive(Debug, Deserialize, Serialize)] +pub struct NoteRequest { + tags: Vec, + body: String, +} + +type NoteRequests = Vec; ``` ### (De)serialization @@ -76,6 +97,19 @@ serde = { version = "1.0", features = ["derive"] } use serde::{Serialize, Deserialize}; ``` +### UUIDs + +UUIDs are unique 128-bit identifiers, stored as 16 octets, and formatted as a hex string in five groups, e.g., `67e55044-10b1-426f-9247-bb680e5fe0c8`. +Have a look at the [Wikipedia entry](http://en.wikipedia.org/wiki/Universally_unique_identifier) for more information. + +```{#dependencies .toml} +uuid = { version = "1.1", features = ["v4","fast-rng","serde"] } +``` + +```{#note_uses .rust} +use uuid::Uuid; +``` + ### Timestamps Timestamps adhere the *RFC 3339 date-time standard* with UTC offset. @@ -96,6 +130,11 @@ use chrono::prelude::{DateTime, Utc}; actix-web = "4.1" ``` +```{#note_uses .rust} +use actix_web::{HttpResponse,Responder,web,get,post}; +use actix_web::http::header::ContentType; +``` + ## Resources - [Tutorial](https://web.archive.org/web/20220710213947/https://hub.qovery.com/guides/tutorial/create-a-blazingly-fast-api-in-rust-part-1/) @@ -109,15 +148,10 @@ Each request handlers is an *async* function that accepts zero or more parameter Here we are requesting the list of notes currently in the system. The function takes 0 parameters and returns a JSON object. -```{#note_uses .rust} -use actix_web::{HttpResponse,get}; -use actix_web::http::header::ContentType; -``` - ```{#req_get_notes .rust} #[get("/notes")] pub async fn list() -> HttpResponse { - let mut notes: Notes = vec![]; + let mut notes: Notes; <> @@ -127,6 +161,25 @@ pub async fn list() -> HttpResponse { } ``` +## POST /notes + +```{#req_post_notes .rust} +#[post("/notes")] +pub async fn add(new_notes: web::Json) -> impl Responder { + let mut new_notes: Notes = + new_notes + .into_inner() + .into_iter() + .map(|n| n.into()) + .collect(); + let count = new_notes.len(); + + <> + + format!("Successfully added {} note(s)", count) +} +``` + # The program The main program is structured as follows @@ -155,6 +208,7 @@ async fn main() -> std::io::Result<()> { HttpServer::new(|| { App::new() .service(note::list) + .service(note::add) }) .bind(("127.0.0.1", 8080))? .run() @@ -164,17 +218,38 @@ async fn main() -> std::io::Result<()> { # Testing +## Using a file as a backend + +This is a temporary solution until an interface to a database (most likely SQLite) is added. + +```{#dependencies .toml} +serde_json = "1.0" +``` + +```{#note_uses .rust} +use std::fs::File; +``` + +### Retrieving notes + ```{#notes_retrieve .rust} -notes.push(Note::new( - String::from("This is a first test note!"), - vec![String::from("test"), String::from("alpha"), String::from("literate")])); -notes.push(Note::new( - String::from("This is my favourite emoji: 🦥"), - vec![String::from("test"), String::from("emoji")])); -notes.push(Note::new( - String::from("Here is a link: https://www.federicoigne.com/"), - vec![String::from("test"), String::from("links")])); +notes = { + let db = File::open("notes.db").expect("Unable to open 'notes.db'"); + serde_json::from_reader(&db).unwrap_or(vec![]) +}; +``` + +### Adding notes + +```{#notes_add .rust} +let mut notes: Notes; +<> +notes.append(&mut new_notes); + +let db = File::create("notes.db").expect("Unable to create/open 'notes.db'"); +serde_json::to_writer(&db,¬es).expect("Unable to write to 'notes.db'"); ``` + # Open questions - Should one be able to delete notes? Or mark them as read/processed? -- cgit v1.2.3