From 95ce885dbc1f8c5c431c7607a054d08da7392867 Mon Sep 17 00:00:00 2001 From: Vika Date: Mon, 7 Mar 2022 11:36:19 +0300 Subject: Port onboarding --- src/frontend/mod.rs | 71 ++++++++++++++++++++++++++++++++++-- src/frontend/templates/onboarding.rs | 2 +- src/main.rs | 6 ++- src/micropub/mod.rs | 18 +++++++-- 4 files changed, 88 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/frontend/mod.rs b/src/frontend/mod.rs index ad50161..49faf59 100644 --- a/src/frontend/mod.rs +++ b/src/frontend/mod.rs @@ -126,10 +126,17 @@ struct OnboardingFeed { struct OnboardingData { user: serde_json::Value, first_post: serde_json::Value, + #[serde(default = "OnboardingData::default_blog_name")] blog_name: String, feeds: Vec, } +impl OnboardingData { + fn default_blog_name() -> String { + "Kitty Box!".to_owned() + } +} + /*pub async fn onboarding_receiver(mut req: Request>) -> Result { use serde_json::json; @@ -268,23 +275,79 @@ pub fn homepage(db: D, endpoints: IndiewebEndpoints) -> impl Filter< let feeds = db.get_channels(&owner).await.unwrap_or_default(); match content { (Some(card), Some(feed), StatusCode::OK) => { - warp::reply::html(Template { + Box::new(warp::reply::html(Template { title: &blog_name, blog_name: &blog_name, endpoints, feeds, user: None, // TODO content: MainPage { feed: &feed, card: &card }.to_string() - }.to_string()) + }.to_string())) as Box }, - _ => { + (None, None, StatusCode::NOT_FOUND) => { // TODO Onboarding - todo!("Onboarding flow") + Box::new(warp::redirect::found( + hyper::Uri::from_static("/onboarding") + )) as Box + } + _ => { + todo!("Handle cases where either main h-card or main h-feed are deleted") } } }) } +pub fn onboarding( + db: D, endpoints: IndiewebEndpoints, http: hyper::Client +) -> impl Filter + Clone { + let inject_db = move || db.clone(); + warp::get() + .map(move || warp::reply::html(Template { + title: "Kittybox - Onboarding", + blog_name: "Kittybox", + endpoints: endpoints.clone(), + feeds: vec![], + user: None, + content: OnboardingPage {}.to_string() + }.to_string())) + .or(warp::post() + .and(crate::util::require_host()) + .and(warp::any().map(inject_db)) + .and(warp::body::json::()) + .and(warp::any().map(move || http.clone())) + .and_then(|host: warp::host::Authority, db: D, body: OnboardingData, http: _| async move { + let user_uid = format!("https://{}/", host.as_str()); + if db.post_exists(&user_uid).await.map_err(FrontendError::from)? { + + return Ok(warp::redirect(hyper::Uri::from_static("/"))); + } + let user = crate::indieauth::User::new(&user_uid, "https://kittybox.fireburn.ru/", "create"); + if body.user["type"][0] != "h-card" || body.first_post["type"][0] != "h-entry" { + return Err(FrontendError::with_code(StatusCode::BAD_REQUEST, "user and first_post should be an h-card and an h-entry").into()); + } + db.set_setting("site_name", user.me.as_str(), &body.blog_name) + .await + .map_err(FrontendError::from)?; + + let (_, hcard) = { + let mut hcard = body.user; + hcard["properties"]["uid"] = serde_json::json!([&user_uid]); + crate::micropub::normalize_mf2(hcard, &user) + }; + db.put_post(&hcard, &user_uid).await.map_err(FrontendError::from)?; + let (uid, post) = crate::micropub::normalize_mf2(body.first_post, &user); + crate::micropub::_post(user, uid, post, db, http).await.map_err(|e| { + FrontendError { + msg: "Error while posting the first post".to_string(), + source: Some(Box::new(e)), + code: StatusCode::INTERNAL_SERVER_ERROR + } + })?; + Ok::<_, warp::Rejection>(warp::redirect(hyper::Uri::from_static("/"))) + })) + +} + #[forbid(clippy::unwrap_used)] pub fn catchall(db: D, endpoints: IndiewebEndpoints) -> impl Filter + Clone { let inject_db = move || db.clone(); diff --git a/src/frontend/templates/onboarding.rs b/src/frontend/templates/onboarding.rs index 0dfb462..f95e1e6 100644 --- a/src/frontend/templates/onboarding.rs +++ b/src/frontend/templates/onboarding.rs @@ -5,7 +5,7 @@ markup::define! { } script[type="module", src="/static/onboarding.js"] {} link[rel="stylesheet", href="/static/onboarding.css"]; - form.onboarding[action="/", method="POST"] { + form.onboarding[action="", method="POST"] { noscript { p { "Ok, let's be honest. Most of this software doesn't require JS to be enabled " diff --git a/src/main.rs b/src/main.rs index 3ce32af..866fcf3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -131,11 +131,14 @@ async fn main() { microsub: None, }; - // TODO interpret HEAD let homepage = warp::get() .and(warp::path::end()) .and(kittybox::frontend::homepage(database.clone(), endpoints.clone())); + let onboarding = warp::path("onboarding") + .and(warp::path::end()) + .and(kittybox::frontend::onboarding(database.clone(), endpoints.clone(), http_client.clone())); + let micropub = warp::path("micropub") .and(warp::path::end() .and(kittybox::micropub::micropub( @@ -178,6 +181,7 @@ async fn main() { let metrics = warp::path("metrics").and(warp::path::end()).map(kittybox::metrics::gather); let app = homepage + .or(onboarding) .or(metrics .or(health)) .or(static_files) diff --git a/src/micropub/mod.rs b/src/micropub/mod.rs index f3152d7..8f5ca09 100644 --- a/src/micropub/mod.rs +++ b/src/micropub/mod.rs @@ -1,4 +1,5 @@ use std::convert::Infallible; +use std::fmt::Display; use either::Either; use log::warn; use warp::http::StatusCode; @@ -38,7 +39,7 @@ enum ErrorType { } #[derive(Serialize, Deserialize, Debug)] -struct MicropubError { +pub(crate) struct MicropubError { error: ErrorType, error_description: String } @@ -55,6 +56,15 @@ impl From for MicropubError { } } +impl std::error::Error for MicropubError {} + +impl Display for MicropubError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("Micropub error: ")?; + f.write_str(&self.error_description) + } +} + impl From<&MicropubError> for StatusCode { fn from(err: &MicropubError) -> Self { use ErrorType::*; @@ -98,6 +108,7 @@ impl MicropubError { impl warp::reject::Reject for MicropubError {} mod post; +pub(crate) use post::normalize_mf2; #[allow(unused_variables)] pub mod media { @@ -197,7 +208,7 @@ mod util { } // TODO actually save the post to the database and schedule post-processing -async fn _post( +pub(crate) async fn _post( user: crate::indieauth::User, uid: String, mf2: serde_json::Value, @@ -301,7 +312,8 @@ async fn _post