about summary refs log tree commit diff
path: root/kittybox-rs/src/frontend/onboarding.rs
diff options
context:
space:
mode:
Diffstat (limited to 'kittybox-rs/src/frontend/onboarding.rs')
-rw-r--r--kittybox-rs/src/frontend/onboarding.rs142
1 files changed, 142 insertions, 0 deletions
diff --git a/kittybox-rs/src/frontend/onboarding.rs b/kittybox-rs/src/frontend/onboarding.rs
new file mode 100644
index 0000000..18def1d
--- /dev/null
+++ b/kittybox-rs/src/frontend/onboarding.rs
@@ -0,0 +1,142 @@
+use kittybox_templates::{ErrorPage, Template, OnboardingPage};
+use crate::database::{Storage, Settings};
+use axum::{
+    Json,
+    extract::{Host, Extension},
+    http::StatusCode,
+    response::{Html, IntoResponse},
+};
+use serde::Deserialize;
+use tracing::{debug, error};
+
+use super::FrontendError;
+
+pub async fn get() -> Html<String> {
+    Html(Template {
+        title: "Kittybox - Onboarding",
+        blog_name: "Kittybox",
+        feeds: vec![],
+        endpoints: None,
+        user: None,
+        content: OnboardingPage {}.to_string()
+    }.to_string())
+}
+
+#[derive(Deserialize, Debug)]
+struct OnboardingFeed {
+    slug: String,
+    name: String,
+}
+
+#[derive(Deserialize, Debug)]
+pub 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()
+    }
+}
+
+#[tracing::instrument(skip(db, http))]
+async fn onboard<D: Storage + 'static>(
+    db: D, user_uid: url::Url, data: OnboardingData, http: reqwest::Client
+) -> Result<(), FrontendError> {
+    // Create a user to pass to the backend
+    // At this point the site belongs to nobody, so it is safe to do
+    let user = crate::indieauth::User::new(
+        user_uid.as_str(),
+        "https://kittybox.fireburn.ru/",
+        "create"
+    );
+
+    if data.user["type"][0] != "h-card" || data.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"
+        ))
+    }
+
+    db.set_setting(Settings::SiteName, user.me.as_str(), &data.blog_name)
+        .await
+        .map_err(FrontendError::from)?;
+
+    let (_, hcard) = {
+        let mut hcard = data.user;
+        hcard["properties"]["uid"] = serde_json::json!([&user_uid]);
+        crate::micropub::normalize_mf2(hcard, &user)
+    };
+    db.put_post(&hcard, user_uid.as_str()).await.map_err(FrontendError::from)?;
+
+    debug!("Creating feeds...");
+    for feed in data.feeds {
+        if feed.name.is_empty() || feed.slug.is_empty() {
+            continue;
+        };
+        log::debug!("Creating feed {} with slug {}", &feed.name, &feed.slug);
+        let (_, feed) = crate::micropub::normalize_mf2(
+            serde_json::json!({
+                "type": ["h-feed"],
+                "properties": {"name": [feed.name], "mp-slug": [feed.slug]}
+            }),
+            &user,
+        );
+
+        db.put_post(&feed, user_uid.as_str()).await.map_err(FrontendError::from)?;
+    }
+    let (uid, post) = crate::micropub::normalize_mf2(data.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(())
+}
+
+pub async fn post<D: Storage + 'static>(
+    Extension(db): Extension<D>,
+    Host(host): Host,
+    Json(data): Json<OnboardingData>,
+    Extension(http): Extension<reqwest::Client>
+) -> axum::response::Response {
+    let user_uid = format!("https://{}/", host.as_str());
+
+    if db.post_exists(&user_uid).await.unwrap() {
+        IntoResponse::into_response((
+            StatusCode::FOUND,
+            [("Location", "/")]
+        ))
+    } else {
+        match onboard(db, user_uid.parse().unwrap(), data, http).await {
+            Ok(()) => IntoResponse::into_response((
+                StatusCode::FOUND,
+                [("Location", "/")]
+            )),
+            Err(err) => {
+                error!("Onboarding error: {}", err);
+                IntoResponse::into_response((
+                    err.code(),
+                    Html(Template {
+                        title: "Kittybox - Onboarding",
+                        blog_name: "Kittybox",
+                        feeds: vec![],
+                        endpoints: None,
+                        user: None,
+                        content: ErrorPage {
+                            code: err.code(),
+                            msg: Some(err.msg().to_string()),
+                        }.to_string(),
+                    }.to_string())
+                ))
+            }
+        }
+    }
+}