about summary refs log tree commit diff
diff options
context:
space:
mode:
authorVika <vika@fireburn.ru>2022-03-07 11:36:19 +0300
committerVika <vika@fireburn.ru>2022-03-07 11:36:19 +0300
commit95ce885dbc1f8c5c431c7607a054d08da7392867 (patch)
tree2a0687ffb2af5b81a85ee89d6fb7dca8951cc731
parentf4f75e6e21e014aed06a3dde1474873cbe4fa2c6 (diff)
downloadkittybox-95ce885dbc1f8c5c431c7607a054d08da7392867.tar.zst
Port onboarding
-rw-r--r--src/frontend/mod.rs71
-rw-r--r--src/frontend/templates/onboarding.rs2
-rw-r--r--src/main.rs6
-rw-r--r--src/micropub/mod.rs18
4 files changed, 88 insertions, 9 deletions
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<OnboardingFeed>,
 }
 
+impl OnboardingData {
+    fn default_blog_name() -> String {
+        "Kitty Box!".to_owned()
+    }
+}
+
 /*pub async fn onboarding_receiver<S: Storage>(mut req: Request<ApplicationState<S>>) -> Result {
     use serde_json::json;
 
@@ -268,23 +275,79 @@ pub fn homepage<D: Storage>(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<dyn warp::Reply>
                 },
-                _ => {
+                (None, None, StatusCode::NOT_FOUND) => {
                     // TODO Onboarding
-                    todo!("Onboarding flow")
+                    Box::new(warp::redirect::found(
+                        hyper::Uri::from_static("/onboarding")
+                    )) as Box<dyn warp::Reply>
+                }
+                _ => {
+                    todo!("Handle cases where either main h-card or main h-feed are deleted")
                 }
             }
         })
 }
 
+pub fn onboarding<D: Storage, T: hyper::client::connect::Connect + Clone + Send + Sync + 'static>(
+    db: D, endpoints: IndiewebEndpoints, http: hyper::Client<T, hyper::Body>
+) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + 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::<OnboardingData>())
+            .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<D: Storage>(db: D, endpoints: IndiewebEndpoints) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + 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<StorageError> 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<D: Storage, T: hyper::client::connect::Connect + Clone + Send + Sync + 'static>(
+pub(crate) async fn _post<D: Storage, T: hyper::client::connect::Connect + Clone + Send + Sync + 'static>(
     user: crate::indieauth::User,
     uid: String,
     mf2: serde_json::Value,
@@ -301,7 +312,8 @@ async fn _post<D: Storage, T: hyper::client::connect::Connect + Clone + Send + S
         contextually_significant_posts.dedup();
 
         // TODO: Make a stream to fetch all these posts and convert them to MF2
-        
+        drop(http);
+
         todo!()
     });