diff options
Diffstat (limited to 'kittybox-rs/src')
-rw-r--r-- | kittybox-rs/src/frontend/mod.rs | 4 | ||||
-rw-r--r-- | kittybox-rs/src/frontend/onboarding.rs | 99 | ||||
-rw-r--r-- | kittybox-rs/src/indieauth.rs | 9 | ||||
-rw-r--r-- | kittybox-rs/src/main.rs | 10 | ||||
-rw-r--r-- | kittybox-rs/src/media/mod.rs | 10 | ||||
-rw-r--r-- | kittybox-rs/src/media/storage/file.rs | 14 | ||||
-rw-r--r-- | kittybox-rs/src/media/storage/mod.rs | 13 | ||||
-rw-r--r-- | kittybox-rs/src/metrics.rs | 2 | ||||
-rw-r--r-- | kittybox-rs/src/micropub/mod.rs | 145 |
9 files changed, 173 insertions, 133 deletions
diff --git a/kittybox-rs/src/frontend/mod.rs b/kittybox-rs/src/frontend/mod.rs index 51db2e1..bc9925f 100644 --- a/kittybox-rs/src/frontend/mod.rs +++ b/kittybox-rs/src/frontend/mod.rs @@ -12,9 +12,7 @@ use tracing::{debug, error}; //pub mod login; pub mod onboarding; -use kittybox_templates::{ - Entry, ErrorPage, Feed, MainPage, Template, VCard, POSTS_PER_PAGE, -}; +use kittybox_templates::{Entry, ErrorPage, Feed, MainPage, Template, VCard, POSTS_PER_PAGE}; pub use kittybox_util::IndiewebEndpoints; diff --git a/kittybox-rs/src/frontend/onboarding.rs b/kittybox-rs/src/frontend/onboarding.rs index 18def1d..9027201 100644 --- a/kittybox-rs/src/frontend/onboarding.rs +++ b/kittybox-rs/src/frontend/onboarding.rs @@ -1,25 +1,28 @@ -use kittybox_templates::{ErrorPage, Template, OnboardingPage}; -use crate::database::{Storage, Settings}; +use crate::database::{Settings, Storage}; use axum::{ - Json, - extract::{Host, Extension}, + extract::{Extension, Host}, http::StatusCode, response::{Html, IntoResponse}, + Json, }; +use kittybox_templates::{ErrorPage, OnboardingPage, Template}; 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()) + Html( + Template { + title: "Kittybox - Onboarding", + blog_name: "Kittybox", + feeds: vec![], + endpoints: None, + user: None, + content: OnboardingPage {}.to_string(), + } + .to_string(), + ) } #[derive(Deserialize, Debug)] @@ -45,21 +48,21 @@ impl OnboardingData { #[tracing::instrument(skip(db, http))] async fn onboard<D: Storage + 'static>( - db: D, user_uid: url::Url, data: OnboardingData, http: reqwest::Client + 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" - ); + 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" - )) + "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) @@ -71,7 +74,9 @@ async fn onboard<D: Storage + 'static>( 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)?; + db.put_post(&hcard, user_uid.as_str()) + .await + .map_err(FrontendError::from)?; debug!("Creating feeds..."); for feed in data.feeds { @@ -87,16 +92,18 @@ async fn onboard<D: Storage + 'static>( &user, ); - db.put_post(&feed, user_uid.as_str()).await.map_err(FrontendError::from)?; + 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 { + 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 - } - })?; + code: StatusCode::INTERNAL_SERVER_ERROR, + })?; Ok(()) } @@ -105,36 +112,34 @@ pub async fn post<D: Storage + 'static>( Extension(db): Extension<D>, Host(host): Host, Json(data): Json<OnboardingData>, - Extension(http): Extension<reqwest::Client> + 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", "/")] - )) + IntoResponse::into_response((StatusCode::FOUND, [("Location", "/")])) } else { match onboard(db, user_uid.parse().unwrap(), data, http).await { - Ok(()) => IntoResponse::into_response(( - StatusCode::FOUND, - [("Location", "/")] - )), + 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()) + 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(), + ), )) } } diff --git a/kittybox-rs/src/indieauth.rs b/kittybox-rs/src/indieauth.rs index 63de859..103f514 100644 --- a/kittybox-rs/src/indieauth.rs +++ b/kittybox-rs/src/indieauth.rs @@ -151,7 +151,10 @@ where { type Rejection = IndieAuthError; - #[cfg_attr(all(debug_assertions, not(test)), allow(unreachable_code, unused_variables))] + #[cfg_attr( + all(debug_assertions, not(test)), + allow(unreachable_code, unused_variables) + )] async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> { // Return a fake user if we're running a debug build // I don't wanna bother with authentication @@ -159,7 +162,7 @@ where return Ok(User::new( "http://localhost:8080/", "https://quill.p3k.io/", - "create update delete media" + "create update delete media", )); let TypedHeader(Authorization(token)) = @@ -232,7 +235,7 @@ mod tests { use super::User; use axum::{ extract::FromRequest, - http::{Method, Request} + http::{Method, Request}, }; use httpmock::prelude::*; diff --git a/kittybox-rs/src/main.rs b/kittybox-rs/src/main.rs index ef051ba..ece31df 100644 --- a/kittybox-rs/src/main.rs +++ b/kittybox-rs/src/main.rs @@ -197,15 +197,17 @@ async fn main() { .route( "/.kittybox/onboarding", axum::routing::get(kittybox::frontend::onboarding::get) - .post(kittybox::frontend::onboarding::post::<FileStorage>) + .post(kittybox::frontend::onboarding::post::<FileStorage>), ) .route( "/.kittybox/micropub", axum::routing::get(kittybox::micropub::query::<FileStorage>) .post(kittybox::micropub::post::<FileStorage>) - .layer(tower_http::cors::CorsLayer::new() - .allow_methods([axum::http::Method::GET, axum::http::Method::POST]) - .allow_origin(tower_http::cors::Any)), + .layer( + tower_http::cors::CorsLayer::new() + .allow_methods([axum::http::Method::GET, axum::http::Method::POST]) + .allow_origin(tower_http::cors::Any), + ), ) .route( "/.kittybox/micropub/client", diff --git a/kittybox-rs/src/media/mod.rs b/kittybox-rs/src/media/mod.rs index d18cf34..0d26f92 100644 --- a/kittybox-rs/src/media/mod.rs +++ b/kittybox-rs/src/media/mod.rs @@ -1,9 +1,9 @@ -use bytes::buf::Buf; -use futures_util::StreamExt; use axum::{ - extract::{Host, Extension, Multipart}, - response::{Response, IntoResponse, Json} + extract::{Extension, Host, Multipart}, + response::{IntoResponse, Json, Response}, }; +use bytes::buf::Buf; +use futures_util::StreamExt; pub mod storage; use storage::{MediaStore, MediaStoreError}; @@ -36,7 +36,7 @@ use storage::{MediaStore, MediaStoreError}; pub async fn upload<S: MediaStore>( Host(host): Host, upload: Multipart, - Extension(db): Extension<S> + Extension(db): Extension<S>, ) -> Response { todo!() } diff --git a/kittybox-rs/src/media/storage/file.rs b/kittybox-rs/src/media/storage/file.rs index 8c0ddf0..ea1f010 100644 --- a/kittybox-rs/src/media/storage/file.rs +++ b/kittybox-rs/src/media/storage/file.rs @@ -22,16 +22,22 @@ impl From<tokio::io::Error> for MediaStoreError { #[async_trait] impl MediaStore for FileStore { async fn write_streaming( - &self, domain: url::Host, filename: &str, - content: axum::extract::multipart::Field<'_> + &self, + domain: url::Host, + filename: &str, + content: axum::extract::multipart::Field<'_>, ) -> Result<()> { todo!() } - async fn read_streaming(&self, domain: url::Host, filename: &str) -> Result<tokio_util::io::ReaderStream<Box<dyn tokio::io::AsyncRead>>> { + async fn read_streaming( + &self, + domain: url::Host, + filename: &str, + ) -> Result<tokio_util::io::ReaderStream<Box<dyn tokio::io::AsyncRead>>> { todo!() } - + async fn write(&self, domain: url::Host, filename: &str, content: &[u8]) -> Result<()> { let path = self.base.join(format!("{}/{}", domain, filename)); diff --git a/kittybox-rs/src/media/storage/mod.rs b/kittybox-rs/src/media/storage/mod.rs index e9b01f9..ba880ab 100644 --- a/kittybox-rs/src/media/storage/mod.rs +++ b/kittybox-rs/src/media/storage/mod.rs @@ -45,9 +45,18 @@ pub type Result<T> = std::result::Result<T, MediaStoreError>; #[async_trait] pub trait MediaStore: 'static + Send + Sync + Clone { - async fn write_streaming(&self, domain: url::Host, filename: &str, content: axum::extract::multipart::Field<'_>) -> Result<()>; + async fn write_streaming( + &self, + domain: url::Host, + filename: &str, + content: axum::extract::multipart::Field<'_>, + ) -> Result<()>; async fn write(&self, domain: url::Host, filename: &str, content: &[u8]) -> Result<()>; - async fn read_streaming(&self, domain: url::Host, filename: &str) -> Result<tokio_util::io::ReaderStream<Box<dyn tokio::io::AsyncRead>>>; + async fn read_streaming( + &self, + domain: url::Host, + filename: &str, + ) -> Result<tokio_util::io::ReaderStream<Box<dyn tokio::io::AsyncRead>>>; async fn read(&self, domain: url::Host, filename: &str) -> Result<Vec<u8>>; async fn delete(&self, domain: url::Host, filename: &str) -> Result<()>; } diff --git a/kittybox-rs/src/metrics.rs b/kittybox-rs/src/metrics.rs index 48f5d9b..e13fcb9 100644 --- a/kittybox-rs/src/metrics.rs +++ b/kittybox-rs/src/metrics.rs @@ -1,8 +1,8 @@ #![allow(unused_imports, dead_code)] use async_trait::async_trait; use lazy_static::lazy_static; -use std::time::{Duration, Instant}; use prometheus::Encoder; +use std::time::{Duration, Instant}; // TODO: Vendor in the Metrics struct from warp_prometheus and rework the path matching algorithm diff --git a/kittybox-rs/src/micropub/mod.rs b/kittybox-rs/src/micropub/mod.rs index d7be785..d8a84e6 100644 --- a/kittybox-rs/src/micropub/mod.rs +++ b/kittybox-rs/src/micropub/mod.rs @@ -1,15 +1,15 @@ use crate::database::{MicropubChannel, Storage, StorageError}; use crate::indieauth::User; use crate::micropub::util::form_to_mf2_json; -use axum::TypedHeader; use axum::extract::{BodyStream, Query}; use axum::headers::ContentType; use axum::response::{IntoResponse, Response}; +use axum::TypedHeader; use axum::{http::StatusCode, Extension}; -use tracing::{error, info, warn, debug}; use serde::{Deserialize, Serialize}; use serde_json::json; use std::fmt::Display; +use tracing::{debug, error, info, warn}; #[derive(Serialize, Deserialize, Debug, PartialEq)] #[serde(rename_all = "kebab-case")] @@ -91,7 +91,7 @@ impl axum::response::IntoResponse for MicropubError { fn into_response(self) -> axum::response::Response { axum::response::IntoResponse::into_response(( StatusCode::from(&self), - axum::response::Json(self) + axum::response::Json(self), )) } } @@ -374,11 +374,8 @@ pub(crate) async fn _post<D: 'static + Storage>( } } - let reply = IntoResponse::into_response(( - StatusCode::ACCEPTED, - [("Location", uid.as_str())], - () - )); + let reply = + IntoResponse::into_response((StatusCode::ACCEPTED, [("Location", uid.as_str())], ())); tokio::task::spawn(background_processing(db, mf2, http)); @@ -486,11 +483,14 @@ async fn post_action<D: Storage>( enum PostBody { Action(MicropubAction), - MF2(serde_json::Value) + MF2(serde_json::Value), } #[tracing::instrument] -async fn dispatch_body(mut body: BodyStream, content_type: ContentType) -> Result<PostBody, MicropubError> { +async fn dispatch_body( + mut body: BodyStream, + content_type: ContentType, +) -> Result<PostBody, MicropubError> { let body: Vec<u8> = { debug!("Buffering body..."); use tokio_stream::StreamExt; @@ -538,7 +538,7 @@ async fn dispatch_body(mut body: BodyStream, content_type: ContentType) -> Resul } else { Err(MicropubError::new( ErrorType::UnsupportedMediaType, - "This Content-Type is not recognized. Try application/json instead?" + "This Content-Type is not recognized. Try application/json instead?", )) } } @@ -549,28 +549,28 @@ pub async fn post<D: Storage + 'static>( Extension(http): Extension<reqwest::Client>, user: User, body: BodyStream, - TypedHeader(content_type): TypedHeader<ContentType> + TypedHeader(content_type): TypedHeader<ContentType>, ) -> axum::response::Response { match dispatch_body(body, content_type).await { Ok(PostBody::Action(action)) => match post_action(action, db, user).await { Ok(()) => Response::default(), - Err(err) => err.into_response() + Err(err) => err.into_response(), }, Ok(PostBody::MF2(mf2)) => { let (uid, mf2) = normalize_mf2(mf2, &user); match _post(user, uid, mf2, db, http).await { Ok(response) => response, - Err(err) => err.into_response() + Err(err) => err.into_response(), } - }, - Err(err) => err.into_response() + } + Err(err) => err.into_response(), } } pub async fn query<D: Storage>( Extension(db): Extension<D>, Query(query): Query<MicropubQuery>, - user: User + user: User, ) -> axum::response::Response { let host = axum::http::Uri::try_from(user.me.as_str()) .unwrap() @@ -582,10 +582,13 @@ pub async fn query<D: Storage>( QueryType::Config => { let channels: Vec<MicropubChannel> = match db.get_channels(host.as_str()).await { Ok(chans) => chans, - Err(err) => return MicropubError::new( - ErrorType::InternalServerError, - &format!("Error fetching channels: {}", err) - ).into_response(), + Err(err) => { + return MicropubError::new( + ErrorType::InternalServerError, + &format!("Error fetching channels: {}", err), + ) + .into_response() + } }; axum::response::Json(json!({ @@ -598,50 +601,59 @@ pub async fn query<D: Storage>( "channels": channels, "_kittybox_authority": host.as_str(), "syndicate-to": [] - })).into_response() - }, + })) + .into_response() + } QueryType::Source => { match query.url { Some(url) => { - if axum::http::Uri::try_from(&url).unwrap().authority().unwrap() != &host { + if axum::http::Uri::try_from(&url) + .unwrap() + .authority() + .unwrap() + != &host + { return MicropubError::new( ErrorType::NotAuthorized, - "You are requesting a post from a website that doesn't belong to you." - ).into_response() + "You are requesting a post from a website that doesn't belong to you.", + ) + .into_response(); } match db.get_post(&url).await { Ok(some) => match some { Some(post) => axum::response::Json(&post).into_response(), None => MicropubError::new( ErrorType::NotFound, - "The specified MF2 object was not found in database." - ).into_response() + "The specified MF2 object was not found in database.", + ) + .into_response(), }, Err(err) => MicropubError::new( ErrorType::InternalServerError, - &format!("Backend error: {}", err) - ).into_response() + &format!("Backend error: {}", err), + ) + .into_response(), } - }, + } None => { // Here, one should probably attempt to query at least the main feed and collect posts // Using a pre-made query function can't be done because it does unneeded filtering // Don't implement for now, this is optional MicropubError::new( ErrorType::InvalidRequest, - "Querying for post list is not implemented yet." - ).into_response() + "Querying for post list is not implemented yet.", + ) + .into_response() } } - }, - QueryType::Channel => { - match db.get_channels(host.as_str()).await { - Ok(chans) => axum::response::Json(json!({"channels": chans})).into_response(), - Err(err) => MicropubError::new( - ErrorType::InternalServerError, - &format!("Error fetching channels: {}", err) - ).into_response() - } + } + QueryType::Channel => match db.get_channels(host.as_str()).await { + Ok(chans) => axum::response::Json(json!({ "channels": chans })).into_response(), + Err(err) => MicropubError::new( + ErrorType::InternalServerError, + &format!("Error fetching channels: {}", err), + ) + .into_response(), }, QueryType::SyndicateTo => { axum::response::Json(json!({ "syndicate-to": [] })).into_response() @@ -725,16 +737,16 @@ mod tests { let user = crate::indieauth::User::new( "https://localhost:8080/", "https://kittybox.fireburn.ru/", - "profile" + "profile", ); let (uid, mf2) = super::normalize_mf2(post, &user); - - let err = super::_post( - user, uid, mf2, db.clone(), reqwest::Client::new() - ).await.unwrap_err(); + + let err = super::_post(user, uid, mf2, db.clone(), reqwest::Client::new()) + .await + .unwrap_err(); assert_eq!(err.error, super::ErrorType::InvalidScope); - + let hashmap = db.mapping.read().await; assert!(hashmap.is_empty()); } @@ -754,21 +766,20 @@ mod tests { let user = crate::indieauth::User::new( "https://aaronparecki.com/", "https://kittybox.fireburn.ru/", - "create update media" + "create update media", ); let (uid, mf2) = super::normalize_mf2(post, &user); - - let err = super::_post( - user, uid, mf2, db.clone(), reqwest::Client::new() - ).await.unwrap_err(); + + let err = super::_post(user, uid, mf2, db.clone(), reqwest::Client::new()) + .await + .unwrap_err(); assert_eq!(err.error, super::ErrorType::Forbidden); - + let hashmap = db.mapping.read().await; assert!(hashmap.is_empty()); } - #[tokio::test] async fn test_post_mf2() { let db = crate::database::MemoryStorage::new(); @@ -782,31 +793,37 @@ mod tests { let user = crate::indieauth::User::new( "https://localhost:8080/", "https://kittybox.fireburn.ru/", - "create" + "create", ); let (uid, mf2) = super::normalize_mf2(post, &user); - let res = super::_post( - user, uid, mf2, db.clone(), reqwest::Client::new() - ).await.unwrap(); + let res = super::_post(user, uid, mf2, db.clone(), reqwest::Client::new()) + .await + .unwrap(); assert!(res.headers().contains_key("Location")); let location = res.headers().get("Location").unwrap(); assert!(db.post_exists(location.to_str().unwrap()).await.unwrap()); - assert!(db.post_exists("https://localhost:8080/feeds/main").await.unwrap()); + assert!(db + .post_exists("https://localhost:8080/feeds/main") + .await + .unwrap()); } #[tokio::test] async fn test_query_foreign_url() { let mut res = super::query( axum::Extension(crate::database::MemoryStorage::new()), - axum::extract::Query(super::MicropubQuery::source("https://aaronparecki.com/feeds/main")), + axum::extract::Query(super::MicropubQuery::source( + "https://aaronparecki.com/feeds/main", + )), crate::indieauth::User::new( "https://fireburn.ru/", "https://quill.p3k.io/", - "create update media" - ) - ).await; + "create update media", + ), + ) + .await; assert_eq!(res.status(), 401); let body = res.body_mut().data().await.unwrap().unwrap(); |