From 5610a5f0bf1a9df02bd3d5b55e2cdebef2440360 Mon Sep 17 00:00:00 2001 From: Vika Date: Tue, 24 May 2022 17:18:30 +0300 Subject: flake.nix: reorganize - Kittybox's source code is moved to a subfolder - This improves build caching by Nix since it doesn't take changes to other files into account - Package and test definitions were spun into separate files - This makes my flake.nix much easier to navigate - This also makes it somewhat possible to use without flakes (but it is still not easy, so use flakes!) - Some attributes were moved in compliance with Nix 2.8's changes to flake schema --- kittybox-rs/templates/src/lib.rs | 347 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 347 insertions(+) create mode 100644 kittybox-rs/templates/src/lib.rs (limited to 'kittybox-rs/templates/src/lib.rs') diff --git a/kittybox-rs/templates/src/lib.rs b/kittybox-rs/templates/src/lib.rs new file mode 100644 index 0000000..39f1075 --- /dev/null +++ b/kittybox-rs/templates/src/lib.rs @@ -0,0 +1,347 @@ +mod templates; +pub use templates::{ErrorPage, MainPage, Template, POSTS_PER_PAGE, Entry, VCard, Feed}; +mod onboarding; +pub use onboarding::OnboardingPage; +mod login; +pub use login::LoginPage; + +#[cfg(test)] +mod tests { + use faker_rand::lorem::Word; + use serde_json::json; + use microformats::types::{Document, Item, PropertyValue, Url}; + use std::cell::RefCell; + use std::rc::Rc; + use faker_rand::en_us::internet::Domain; + + enum PostType { + Note, + Article, + ReplyTo(serde_json::Value), + ReplyToLink(String), + LikeOf(serde_json::Value), + LikeOfLink(String) + } + + fn gen_hcard(domain: &str) -> serde_json::Value { + use faker_rand::en_us::names::FirstName; + + json!({ + "type": ["h-card"], + "properties": { + "name": [rand::random::().to_string()], + "photo": [format!("https://{domain}/media/me.png")], + "uid": [format!("https://{domain}/")], + "url": [format!("https://{domain}/")] + } + }) + } + + fn gen_random_post(domain: &str, kind: PostType) -> serde_json::Value { + use faker_rand::lorem::{Paragraph, Sentence}; + + fn html(content: Paragraph) -> serde_json::Value { + json!({ + "html": format!("

{}

", content), + "value": content.to_string() + }) + } + + let uid = format!( + "https://{domain}/posts/{}-{}-{}", + rand::random::(), rand::random::(), rand::random::() + ); + let dt = chrono::offset::Local::now() + .to_rfc3339_opts(chrono::SecondsFormat::Secs, true); + + match kind { + PostType::Note => { + let content = rand::random::(); + + json!({ + "type": ["h-entry"], + "properties": { + "content": [html(content)], + "published": [dt], + "uid": [&uid], "url": [&uid], + "author": [gen_hcard(domain)] + } + }) + } + PostType::Article => { + let content = rand::random::(); + let name = rand::random::(); + + json!({ + "type": ["h-entry"], + "properties": { + "content": [html(content)], + "published": [dt], + "uid": [&uid], "url": [&uid], + "author": [gen_hcard(domain)], + "name": [name.to_string()] + } + }) + } + PostType::ReplyTo(ctx) => { + let content = rand::random::(); + + json!({ + "type": ["h-entry"], + "properties": { + "content": [html(content)], + "published": [dt], + "uid": [&uid], "url": [&uid], + "author": [gen_hcard(domain)], + "in-reply-to": [{ + "type": ["h-cite"], + "properties": ctx["properties"] + }] + } + }) + }, + PostType::ReplyToLink(link) => { + let content = rand::random::(); + + json!({ + "type": ["h-entry"], + "properties": { + "content": [html(content)], + "published": [dt], + "uid": [&uid], "url": [&uid], + "author": [gen_hcard(domain)], + "in-reply-to": [link] + } + }) + }, + PostType::LikeOf(ctx) => { + json!({ + "type": ["h-entry"], + "properties": { + "published": [dt], + "author": [gen_hcard(domain)], + "uid": [&uid], "url": [&uid], + "like-of": [{ + "type": ["h-cite"], + "properties": ctx["properties"] + }] + } + }) + }, + PostType::LikeOfLink(link) => { + json!({ + "type": ["h-entry"], + "properties": { + "published": [dt], + "author": [gen_hcard(domain)], + "uid": [&uid], "url": [&uid], + "like-of": [link] + } + }) + } + } + } + + fn check_dt_published( + mf2: &serde_json::Value, + item: &Rc> + ) { + use microformats::types::temporal::Value as TemporalValue; + + let _item = item.borrow(); + let props = _item.properties.borrow(); + assert!(props.contains_key("published")); + + if let Some(PropertyValue::Temporal( + TemporalValue::Timestamp(item) + )) = props.get("published") + .and_then(|v| v.first()) + { + use chrono::{DateTime, FixedOffset, NaiveDateTime}; + + // Faithfully reconstruct the original datetime + // I wonder why not just have an Enum that would + // get you either date, time or a datetime, + // potentially with an offset? + let offset = item.as_offset().unwrap().data; + let ndt: NaiveDateTime = item.as_date().unwrap().data + .and_time(item.as_time().unwrap().data) + // subtract the offset here, since we will add it back + - offset; + let dt = DateTime::::from_utc(ndt, offset); + + let expected: DateTime = chrono::DateTime::parse_from_rfc3339( + mf2["properties"]["published"][0].as_str().unwrap() + ).unwrap(); + + assert_eq!(dt, expected); + } else { + unreachable!() + } + } + + fn check_e_content( + mf2: &serde_json::Value, + item: &Rc> + ) { + let _item = item.borrow(); + let props = _item.properties.borrow(); + assert!(props.contains_key("content")); + + if let Some(PropertyValue::Fragment(content)) = + props.get("content") + .and_then(|v| v.first()) + { + assert_eq!( + content.html, + mf2["properties"]["content"][0]["html"].as_str().unwrap() + ); + } else { + unreachable!() + } + + } + + #[test] + #[ignore = "see https://gitlab.com/maxburon/microformats-parser/-/issues/7"] + fn test_note() { + test_logger::ensure_env_logger_initialized(); + + let mf2 = gen_random_post( + &rand::random::().to_string(), + PostType::Note + ); + + let html = crate::templates::Entry { + post: &mf2 + }.to_string(); + + let url: Url = mf2.pointer("/properties/uid/0") + .and_then(|i| i.as_str()) + .and_then(|u| u.parse().ok()) + .unwrap(); + let parsed: Document = microformats::from_html(&html, url.clone()).unwrap(); + + if let Some(PropertyValue::Item(item)) = parsed.get_item_by_url(&url) { + let _item = item.borrow(); + let props = _item.properties.borrow(); + + check_e_content(&mf2, &item); + check_dt_published(&mf2, &item); + assert!(props.contains_key("uid")); + assert!(props.contains_key("url")); + assert!(props.get("url") + .unwrap() + .iter() + .any(|i| i == props.get("uid").and_then(|v| v.first()).unwrap())); + // XXX: fails because of https://gitlab.com/maxburon/microformats-parser/-/issues/7 + assert!(!props.contains_key("name")); + + } else { + unreachable!() + } + } + + #[test] + fn test_article() { + test_logger::ensure_env_logger_initialized(); + + let mf2 = gen_random_post( + &rand::random::().to_string(), + PostType::Article + ); + let html = crate::templates::Entry { + post: &mf2 + }.to_string(); + let url: Url = mf2.pointer("/properties/uid/0") + .and_then(|i| i.as_str()) + .and_then(|u| u.parse().ok()) + .unwrap(); + let parsed: Document = microformats::from_html(&html, url.clone()).unwrap(); + + if let Some(PropertyValue::Item(item)) = parsed.get_item_by_url(&url) { + let _item = item.borrow(); + let props = _item.properties.borrow(); + + check_e_content(&mf2, &item); + check_dt_published(&mf2, &item); + assert!(props.contains_key("uid")); + assert!(props.contains_key("url")); + assert!(props.get("url") + .unwrap() + .iter() + .any(|i| i == props.get("uid").and_then(|v| v.first()).unwrap())); + assert!(props.contains_key("name")); + if let Some(PropertyValue::Plain(name)) = props.get("name").and_then(|v| v.first()) { + assert_eq!( + name, + mf2.pointer("/properties/name/0") + .and_then(|v| v.as_str()) + .unwrap() + ); + } else { + panic!("Name wasn't a plain property!"); + } + } else { + unreachable!() + } + } + + #[test] + fn test_like_of() { + test_logger::ensure_env_logger_initialized(); + + for likeof in [ + PostType::LikeOf(gen_random_post( + &rand::random::().to_string(), + PostType::Note + )), + PostType::LikeOfLink(format!( + "https://{}/posts/{}-{}-{}", + &rand::random::(), + &rand::random::(), + &rand::random::(), + &rand::random::(), + )) + ] { + let mf2 = gen_random_post( + &rand::random::().to_string(), + likeof + ); + let url: Url = mf2.pointer("/properties/uid/0") + .and_then(|i| i.as_str()) + .and_then(|u| u.parse().ok()) + .unwrap(); + let html = crate::templates::Entry { + post: &mf2 + }.to_string(); + let parsed: Document = microformats::from_html(&html, url.clone()).unwrap(); + + if let Some(item) = parsed.items.get(0) { + let _item = item.borrow(); + let props = _item.properties.borrow(); + + check_dt_published(&mf2, item); + assert!(props.contains_key("like-of")); + match props.get("like-of").and_then(|v| v.first()) { + Some(PropertyValue::Url(url)) => { + assert_eq!( + url, + &mf2.pointer("/properties/like-of/0") + .and_then(|i| i.as_str()) + .or_else(|| mf2.pointer("/properties/like-of/0/properties/uid/0").and_then(|i| i.as_str())) + .and_then(|u| u.parse::().ok()) + .unwrap() + ); + } + Some(PropertyValue::Item(_cite)) => { + todo!() + } + other => panic!("Unexpected value in like-of: {:?}", other) + } + } else { + unreachable!() + } + } + } +} -- cgit 1.4.1