implement basic KV binding

This commit is contained in:
Peter Cai 2020-04-07 20:54:04 +08:00
parent 0547cdd0c3
commit 243a64b7b9
No known key found for this signature in database
GPG Key ID: 71F5FB4E4F3FD54F
5 changed files with 98 additions and 2 deletions

View File

@ -13,6 +13,7 @@ default = ["console_error_panic_hook", "wee_alloc"]
[dependencies]
cfg-if = "0.1.2"
lazy_static = "1.4"
js-sys = "0.3"
serde = { version = "1.0", features = [ "derive" ] }
serde_json = "1.0"
wasm-bindgen = "0.2"

45
src/blog.rs Normal file
View File

@ -0,0 +1,45 @@
// Utility functions and structs for the blogging system
// Due to limitations of the Cloudflare Workers KV, we do not
// store the entire state in one record; instead, different
// parts are stroed in different records. This also increases
// efficiency, since the program won't need to load anything
// unnecessary from KV.
use crate::store;
use crate::utils::*;
use serde::{Serialize, Deserialize};
use std::vec::Vec;
// A list of the UUIDs of all published blog posts
// This should be SORTED with the newest posts at lower indices (closer to 0)
// The user may edit this via KV UI to change ordering and such
// by default new posts are always added to the top
#[derive(Serialize, Deserialize)]
pub struct PostsList(pub Vec<String>);
impl PostsList {
pub async fn load() -> PostsList {
match store::get_obj("posts_list").await {
Ok(v) => PostsList(v),
// Don't panic on empty
// TODO: What if the user messed up when editing?
// That would cause a decode failure and all data will be gone
// if a new post is added or updated (overwriting the KV value)
// under this logic
// (if no new post is added then nothing bad would happen;
// the user would probably notice when trying to visit the blog home page)
Err(_) => PostsList(vec![])
}
}
pub fn has_post(&self, uuid: &str) -> bool {
self.0.contains(&uuid.into())
}
// Add a post to the list and then update the record in KV
// Also consumes self, as this should normally be the last action
// in an API call
pub async fn add_post(mut self, uuid: &str) -> MyResult<()> {
self.0.insert(0, uuid.into());
store::put_obj_pretty("posts_list", self.0).await
}
}

View File

@ -4,6 +4,8 @@ extern crate lazy_static;
#[macro_use]
mod utils;
mod router;
mod store;
mod blog;
mod sn;
use cfg_if::cfg_if;

View File

@ -1,5 +1,5 @@
// Interface for Standard Notes (Actions)
use crate::CONFIG;
use crate::{CONFIG, blog};
use crate::router::Router;
use crate::utils::*;
use serde::{Serialize, Serializer};
@ -28,8 +28,17 @@ async fn get_actions(_req: Request, url: Url) -> MyResult<Response> {
let origin = url.origin();
let mut actions = vec![];
// Show different options depending on whether the post already exists
let post_exists = match params.get("item_uuid") {
Some(uuid) => {
let posts = blog::PostsList::load().await;
posts.has_post(&uuid)
},
None => false
};
actions.push(Action {
label: "Publish".into(),
label: if post_exists { "Update".into() } else { "Publish".into() },
url: format!("{}/post?secret={}", origin, CONFIG.secret.clone()),
verb: Verb::Post,
context: Context::Item,

39
src/store.rs Normal file
View File

@ -0,0 +1,39 @@
// Bindings to Cloudflare Workers KV
use crate::utils::*;
use js_sys::Promise;
use serde::Serialize;
use serde::de::DeserializeOwned;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::JsFuture;
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = PAPRIKA, js_name = "get")]
fn kv_get(key: &str) -> Promise;
#[wasm_bindgen(js_namespace = PAPRIKA, js_name = "put")]
fn kv_put_str(key: &str, value: &str) -> Promise;
}
pub async fn get_str(key: &str) -> MyResult<String> {
Ok(JsFuture::from(kv_get(key)).await.internal_err()?.as_string().unwrap())
}
pub async fn get_obj<T: DeserializeOwned>(key: &str) -> MyResult<T> {
let res = get_str(key).await?;
Ok(serde_json::from_str(&res).internal_err()?)
}
pub async fn put_str(key: &str, value: &str) -> MyResult<()> {
JsFuture::from(kv_put_str(key, value)).await.internal_err()?;
Ok(())
}
pub async fn put_obj<T: Serialize>(key: &str, value: T) -> MyResult<()> {
put_str(key, &serde_json::to_string(&value).internal_err()?).await
}
// Some objects may be available for manual editing; thus making it pretty may be helpful
// For example, the user may want to manually edit the order in which posts appear
pub async fn put_obj_pretty<T: Serialize>(key: &str, value: T) -> MyResult<()> {
put_str(key, &serde_json::to_string_pretty(&value).internal_err()?).await
}