From 8fe31281f00732eaf32145bc119d641510ce0be8 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Wed, 8 Apr 2020 17:39:44 +0800 Subject: [PATCH] support customized metadata --- src/sn.rs | 87 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 80 insertions(+), 7 deletions(-) diff --git a/src/sn.rs b/src/sn.rs index 643050b..47a258b 100644 --- a/src/sn.rs +++ b/src/sn.rs @@ -72,6 +72,72 @@ async fn get_actions(_req: Request, url: Url) -> MyResult { ).internal_err() } +#[derive(Deserialize)] +struct CustomMetadata { + url: Option, + timestamp: Option // Should be something `js_sys::Date::parse` could handle +} + +struct Metadata { + url: String, + has_custom_url: bool, + timestamp: u64, // Seconds + has_custom_timestamp: bool +} + +// You can customize metadata by adding something like +// +// ```json +// { +// "url": "xxx-xxx-xxx", +// "timestamp": "YYYY-mm-dd" +// } +// ``` +// +// to the beginning of your article +// Normally when you update a post, the timestamp and URL will not be updated, +// but when you have custom metadata, they will always be updated. +// When the URL is updated, the old URL will automatically 301 to the new one +fn parse_custom_metadata_from_content(text: String) -> (Option, String) { + if !text.starts_with("```json\n") { + (None, text) + } else { + match text.find("```\n\n") { + None => (None, text), + Some(pos) => { + let json = serde_json::from_str(&text[8..pos]).ok(); + return (json, text[pos + 5..].to_owned()) + } + } + } +} + +// Generate metadata from uuid and title +// Fill in default value if custom value not present +fn build_metadata(custom: Option, uuid: &str, title: &str) -> Metadata { + // Default values + let mut ret = Metadata { + url: title_to_url(&uuid, &title), + has_custom_url: false, + timestamp: Date::now() as u64 / 1000, // Seconds + has_custom_timestamp: false + }; + + if let Some(custom) = custom { + if let Some(url) = custom.url { + ret.url = url; + ret.has_custom_url = true; + } + + if let Some(date) = custom.timestamp { + ret.timestamp = Date::parse(&date) as u64 / 1000; // Seconds + ret.has_custom_timestamp = true; + } + } + + ret +} + async fn create_or_update_post(req: Request, url: Url) -> MyResult { verify_secret!(url, params); if req.method() != "POST" { @@ -88,27 +154,34 @@ async fn create_or_update_post(req: Request, url: Url) -> MyResult { return Err(Error::BadRequest("At least one item must be supplied".into())); } - // TODO: we should support customizing timestamp and URL from text - // and if there are option detected in text, it always overrides - // whatever was stored before - // If the URL is changed via this way, we should make sure the old - // URL actually 301's to the new URL let uuid = data.items[0].uuid.clone(); let text = data.items[0].content.text.clone(); let title = data.items[0].content.title.clone(); + let (custom_metadata, text) = parse_custom_metadata_from_content(text); + let metadata = build_metadata(custom_metadata, &uuid, &title); let post = match blog::Post::find_by_uuid(&uuid).await { Ok(mut post) => { post.content = text; post.title = title; + + // Update metadata if custom ones are present + if metadata.has_custom_url { + post.url = metadata.url; + } + + if metadata.has_custom_timestamp { + post.timestamp = metadata.timestamp; + } + post }, Err(_) => { blog::Post { - url: title_to_url(&uuid, &title), + url: metadata.url, uuid: uuid, title: title, content: text, - timestamp: Date::now() as u64 / 1000 // Seconds + timestamp: metadata.timestamp } } };