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 { 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, } impl OnboardingData { fn default_blog_name() -> String { "Kitty Box!".to_owned() } } #[tracing::instrument(skip(db, http))] async fn onboard( 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( Extension(db): Extension, Host(host): Host, Json(data): Json, Extension(http): Extension ) -> 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()) )) } } } }