Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(api_apub): update openapi docs #71

Merged
merged 5 commits into from
Sep 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions crates/api_apub/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
use hatsu_apub::{
actors::{PublicKeySchema, User, UserAttachment, UserImage},
collections::{Collection, CollectionOrPage, CollectionPage},
links::{Emoji, EmojiIcon, Hashtag, Mention, Tag},
objects::Note,
};
use serde_json::Value;
use url::Url;
use utoipa::OpenApi;
use utoipa_axum::router::OpenApiRouter;

Expand All @@ -14,11 +17,19 @@ pub const TAG: &str = "apub";

#[derive(OpenApi)]
#[openapi(
paths(
posts::notice::notice,
posts::post::post,
),
kwaa marked this conversation as resolved.
Show resolved Hide resolved
components(schemas(
PublicKeySchema,
User,
UserAttachment,
UserImage,
Collection,
CollectionOrPage,
CollectionPage<Url>,
CollectionPage<Value>,
Emoji,
EmojiIcon,
Hashtag,
Expand Down
3 changes: 1 addition & 2 deletions crates/api_apub/src/posts/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@ use utoipa_axum::router::OpenApiRouter;

use crate::ApubApi;

mod notice;
pub mod notice;
pub mod post;

pub fn routes() -> OpenApiRouter {
OpenApiRouter::with_openapi(ApubApi::openapi())
// TODO: writing utoipa docs
.route("/notice/*notice", get(notice::notice))
.route("/posts/*post", get(post::post))
.route("/p/*post", get(post::redirect))
Expand Down
15 changes: 15 additions & 0 deletions crates/api_apub/src/posts/notice.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
use axum::{debug_handler, extract::Path, response::Redirect};
use hatsu_utils::AppError;

use crate::TAG;

/// Get post by base64 url
#[utoipa::path(
get,
tag = TAG,
path = "/notice/{notice}",
responses(
(status = OK, description = "Post", body = Note),
(status = NOT_FOUND, description = "Post does not exist", body = AppError)
),
params(
("notice" = String, Path, description = "Base64 Post Url")
)
)]
#[debug_handler]
pub async fn notice(Path(base64_url): Path<String>) -> Result<Redirect, AppError> {
let base64 = base64_simd::URL_SAFE;
Expand Down
17 changes: 11 additions & 6 deletions crates/api_apub/src/users/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use axum::routing::{get, post};
use utoipa::OpenApi;
use serde::Deserialize;
use utoipa::{IntoParams, OpenApi};
use utoipa_axum::{router::OpenApiRouter, routes};

use crate::ApubApi;
Expand All @@ -9,14 +10,18 @@ mod user_following;
mod user_inbox;
mod user_outbox;

#[derive(Deserialize, IntoParams)]
pub struct Pagination {
page: Option<u64>,
}

pub fn routes() -> OpenApiRouter {
OpenApiRouter::with_openapi(ApubApi::openapi())
.routes(routes!(user::handler))
// TODO: writing utoipa docs
.route("/users/:user/followers", get(user_followers::handler))
.route("/users/:user/following", get(user_following::handler))
.route("/users/:user/outbox", get(user_outbox::handler))
.route("/users/:user/inbox", post(user_inbox::handler))
.routes(routes!(user_followers::handler))
.routes(routes!(user_following::handler))
.routes(routes!(user_inbox::handler))
.routes(routes!(user_outbox::handler))
// fallback routes
.route("/u/:user", get(user::redirect))
.route("/u/:user/followers", get(user_followers::redirect))
Expand Down
39 changes: 23 additions & 16 deletions crates/api_apub/src/users/user_followers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,35 @@ use axum::{
};
use hatsu_apub::{
actors::ApubUser,
collections::{Collection, CollectionPage},
collections::{Collection, CollectionOrPage, CollectionPage},
kwaa marked this conversation as resolved.
Show resolved Hide resolved
};
use hatsu_db_schema::{prelude::ReceivedFollow, received_follow};
use hatsu_utils::{AppData, AppError};
use sea_orm::{ModelTrait, PaginatorTrait, QueryOrder};
use serde::Deserialize;
use serde_json::Value;
use url::Url;

#[derive(Default, Deserialize)]
pub struct Pagination {
page: Option<u64>,
}
use crate::{users::Pagination, TAG};

/// Get user followers
#[utoipa::path(
get,
tag = TAG,
path = "/users/{user}/followers",
responses(
(status = OK, description = "Followers", body = CollectionOrPage),
(status = NOT_FOUND, description = "User does not exist", body = AppError)
),
params(
("user" = String, Path, description = "The Domain of the User in the database."),
Pagination
)
)]
#[debug_handler]
pub async fn handler(
Path(name): Path<String>,
pagination: Option<Query<Pagination>>,
pagination: Query<Pagination>,
data: Data<AppData>,
) -> Result<FederationJson<WithContext<Value>>, AppError> {
let Query(pagination) = pagination.unwrap_or_default();

) -> Result<FederationJson<WithContext<CollectionOrPage>>, AppError> {
let user_id: ObjectId<ApubUser> =
hatsu_utils::url::generate_user_url(data.domain(), &name)?.into();
let user = user_id.dereference_local(&data).await?;
Expand All @@ -48,12 +55,12 @@ pub async fn handler(

match pagination.page {
None => Ok(FederationJson(WithContext::new_default(
serde_json::to_value(Collection::new(
CollectionOrPage::Collection(Collection::new(
&hatsu_utils::url::generate_user_url(data.domain(), &name)?
.join(&format!("{name}/followers"))?,
kwaa marked this conversation as resolved.
Show resolved Hide resolved
total.number_of_items,
Some(total.number_of_pages),
)?)?,
total.number_of_pages,
)?),
))),
Some(page) =>
if page > 1 && page > total.number_of_pages {
Expand All @@ -63,7 +70,7 @@ pub async fn handler(
))
} else {
Ok(FederationJson(WithContext::new_default(
serde_json::to_value(CollectionPage::<Url>::new(
CollectionOrPage::CollectionPageUrl(CollectionPage::<Url>::new(
hatsu_utils::url::generate_user_url(data.domain(), &name)?
.join(&format!("{name}/followers"))?,
kwaa marked this conversation as resolved.
Show resolved Hide resolved
total.number_of_items,
Expand All @@ -76,7 +83,7 @@ pub async fn handler(
.collect(),
total.number_of_pages,
page,
)?)?,
)?),
)))
},
}
Expand Down
39 changes: 23 additions & 16 deletions crates/api_apub/src/users/user_following.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,43 +8,50 @@ use axum::{
extract::{Path, Query},
response::Redirect,
};
use hatsu_apub::collections::{Collection, CollectionPage};
use hatsu_apub::collections::{Collection, CollectionOrPage, CollectionPage};
use hatsu_utils::{AppData, AppError};
use serde::Deserialize;
use serde_json::Value;
use url::Url;

#[derive(Default, Deserialize)]
pub struct Pagination {
page: Option<u64>,
}
use crate::{users::Pagination, TAG};

/// Get user following
#[utoipa::path(
get,
tag = TAG,
path = "/users/{user}/following",
responses(
(status = OK, description = "Following", body = CollectionOrPage),
(status = NOT_FOUND, description = "User does not exist", body = AppError)
),
params(
("user" = String, Path, description = "The Domain of the User in the database."),
Pagination
)
)]
#[debug_handler]
pub async fn handler(
Path(name): Path<String>,
pagination: Option<Query<Pagination>>,
pagination: Query<Pagination>,
data: Data<AppData>,
) -> Result<FederationJson<WithContext<Value>>, AppError> {
let Query(pagination) = pagination.unwrap_or_default();

) -> Result<FederationJson<WithContext<CollectionOrPage>>, AppError> {
match pagination.page {
None => Ok(FederationJson(WithContext::new_default(
serde_json::to_value(Collection::new(
CollectionOrPage::Collection(Collection::new(
&hatsu_utils::url::generate_user_url(data.domain(), &name)?
.join(&format!("{name}/following"))?,
0,
Some(0),
)?)?,
0,
)?),
))),
Some(page) => Ok(FederationJson(WithContext::new_default(
serde_json::to_value(CollectionPage::<Url>::new(
CollectionOrPage::CollectionPageUrl(CollectionPage::<Url>::new(
hatsu_utils::url::generate_user_url(data.domain(), &name)?
.join(&format!("{name}/following"))?,
0,
vec![],
0,
page,
)?)?,
)?),
))),
}
}
Expand Down
16 changes: 16 additions & 0 deletions crates/api_apub/src/users/user_inbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,22 @@ use axum::{debug_handler, response::IntoResponse};
use hatsu_apub::{activities::UserInboxActivities, actors::ApubUser};
use hatsu_utils::AppData;

use crate::TAG;

/// User inbox
#[utoipa::path(
post,
tag = TAG,
path = "/users/{user}/inbox",
responses(
(status = OK),
(status = NOT_FOUND, body = AppError),
(status = INTERNAL_SERVER_ERROR, body = AppError)
),
params(
("user" = String, Path, description = "The Domain of the User in the database.")
)
)]
#[debug_handler]
pub async fn handler(data: Data<AppData>, activity_data: ActivityData) -> impl IntoResponse {
receive_activity::<WithContext<UserInboxActivities>, ApubUser, AppData>(activity_data, &data)
Expand Down
38 changes: 23 additions & 15 deletions crates/api_apub/src/users/user_outbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,35 @@ use axum::{
use hatsu_apub::{
activities::ApubActivity,
actors::ApubUser,
collections::{Collection, CollectionPage},
collections::{Collection, CollectionOrPage, CollectionPage},
};
use hatsu_db_schema::{activity, prelude::Activity};
use hatsu_utils::{AppData, AppError};
use sea_orm::{ColumnTrait, ModelTrait, PaginatorTrait, QueryFilter, QueryOrder};
use serde::Deserialize;
use serde_json::Value;

#[derive(Default, Deserialize)]
pub struct Pagination {
page: Option<u64>,
}
use crate::{users::Pagination, TAG};

/// Get user outbox
#[utoipa::path(
get,
tag = TAG,
path = "/users/{user}/outbox",
responses(
(status = OK, description = "Outbox", body = CollectionOrPage),
(status = NOT_FOUND, description = "User does not exist", body = AppError)
),
params(
("user" = String, Path, description = "The Domain of the User in the database."),
Pagination
)
)]
#[debug_handler]
pub async fn handler(
Path(name): Path<String>,
pagination: Option<Query<Pagination>>,
pagination: Query<Pagination>,
kwaa marked this conversation as resolved.
Show resolved Hide resolved
data: Data<AppData>,
) -> Result<FederationJson<WithContext<Value>>, AppError> {
let Query(pagination) = pagination.unwrap_or_default();

) -> Result<FederationJson<WithContext<CollectionOrPage>>, AppError> {
let user_id: ObjectId<ApubUser> =
hatsu_utils::url::generate_user_url(data.domain(), &name)?.into();
let user = user_id.dereference_local(&data).await?;
Expand All @@ -50,12 +58,12 @@ pub async fn handler(

match pagination.page {
None => Ok(FederationJson(WithContext::new_default(
serde_json::to_value(Collection::new(
CollectionOrPage::Collection(Collection::new(
&hatsu_utils::url::generate_user_url(data.domain(), &name)?
.join(&format!("{name}/outbox"))?,
total.number_of_items,
Some(total.number_of_pages),
)?)?,
total.number_of_pages,
)?),
))),
Some(page) =>
if page > 1 && page > total.number_of_pages {
Expand All @@ -65,7 +73,7 @@ pub async fn handler(
))
} else {
Ok(FederationJson(WithContext::new_default(
serde_json::to_value(CollectionPage::<Value>::new(
CollectionOrPage::CollectionPageValue(CollectionPage::<Value>::new(
hatsu_utils::url::generate_user_url(data.domain(), &name)?
.join(&format!("{name}/outbox"))?,
total.number_of_items,
Expand All @@ -81,7 +89,7 @@ pub async fn handler(
.collect(),
total.number_of_pages,
page,
)?)?,
)?),
)))
},
}
Expand Down
37 changes: 37 additions & 0 deletions crates/apub/src/collections/collection.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use activitypub_federation::kinds::collection::OrderedCollectionType;
use hatsu_utils::AppError;
use serde::{Deserialize, Serialize};
use url::Url;
use utoipa::ToSchema;

use crate::collections::generate_collection_page_url;

#[derive(Clone, Debug, Deserialize, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct Collection {
#[serde(rename = "type")]
pub kind: OrderedCollectionType,
// example: https://hatsu.local/users/example.com/collection
pub id: Url,
// example: https://hatsu.local/users/example.com/collection?page=1
pub first: Url,
// example: https://hatsu.local/users/example.com/collection?page=64
pub last: Url,
// collection count
pub total_items: u64,
}

impl Collection {
pub fn new(collection_id: &Url, total_items: u64, total_pages: u64) -> Result<Self, AppError> {
Ok(Self {
kind: OrderedCollectionType::OrderedCollection,
id: collection_id.clone(),
first: generate_collection_page_url(collection_id, 1)?,
last: generate_collection_page_url(collection_id, match total_pages {
page if page > 0 => page,
_ => 1,
})?,
total_items,
})
}
}
Loading