feat: v2 rewrite — hexagonal arch, ActivityPub federation, NATS, deployment-ready #1
@@ -1,4 +1,5 @@
|
|||||||
pub mod errors;
|
pub mod errors;
|
||||||
|
pub mod openapi;
|
||||||
pub mod extractors;
|
pub mod extractors;
|
||||||
pub mod handlers;
|
pub mod handlers;
|
||||||
pub mod routes;
|
pub mod routes;
|
||||||
|
|||||||
13
crates/presentation/src/openapi/api_keys.rs
Normal file
13
crates/presentation/src/openapi/api_keys.rs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
use utoipa::OpenApi;
|
||||||
|
use api_types::{requests::CreateApiKeyRequest, responses::{ApiKeyResponse, CreatedApiKeyResponse}};
|
||||||
|
|
||||||
|
#[derive(OpenApi)]
|
||||||
|
#[openapi(
|
||||||
|
paths(
|
||||||
|
crate::handlers::api_keys::get_api_keys,
|
||||||
|
crate::handlers::api_keys::post_api_key,
|
||||||
|
crate::handlers::api_keys::delete_api_key_handler,
|
||||||
|
),
|
||||||
|
components(schemas(CreateApiKeyRequest, ApiKeyResponse, CreatedApiKeyResponse))
|
||||||
|
)]
|
||||||
|
pub struct ApiKeysDoc;
|
||||||
9
crates/presentation/src/openapi/auth.rs
Normal file
9
crates/presentation/src/openapi/auth.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
use utoipa::OpenApi;
|
||||||
|
use api_types::{requests::{LoginRequest, RegisterRequest}, responses::{AuthResponse, ErrorResponse}};
|
||||||
|
|
||||||
|
#[derive(OpenApi)]
|
||||||
|
#[openapi(
|
||||||
|
paths(crate::handlers::auth::post_register, crate::handlers::auth::post_login),
|
||||||
|
components(schemas(RegisterRequest, LoginRequest, AuthResponse, ErrorResponse))
|
||||||
|
)]
|
||||||
|
pub struct AuthDoc;
|
||||||
13
crates/presentation/src/openapi/feed.rs
Normal file
13
crates/presentation/src/openapi/feed.rs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
use utoipa::OpenApi;
|
||||||
|
|
||||||
|
#[derive(OpenApi)]
|
||||||
|
#[openapi(
|
||||||
|
paths(
|
||||||
|
crate::handlers::feed::home_feed,
|
||||||
|
crate::handlers::feed::public_feed,
|
||||||
|
crate::handlers::feed::search_handler,
|
||||||
|
crate::handlers::feed::user_thoughts_handler,
|
||||||
|
crate::handlers::feed::tag_thoughts_handler,
|
||||||
|
),
|
||||||
|
)]
|
||||||
|
pub struct FeedDoc;
|
||||||
5
crates/presentation/src/openapi/health.rs
Normal file
5
crates/presentation/src/openapi/health.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
use utoipa::OpenApi;
|
||||||
|
|
||||||
|
#[derive(OpenApi)]
|
||||||
|
#[openapi(paths(crate::handlers::health::health_handler))]
|
||||||
|
pub struct HealthDoc;
|
||||||
61
crates/presentation/src/openapi/mod.rs
Normal file
61
crates/presentation/src/openapi/mod.rs
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
mod api_keys;
|
||||||
|
mod auth;
|
||||||
|
mod feed;
|
||||||
|
mod health;
|
||||||
|
mod notifications;
|
||||||
|
mod social;
|
||||||
|
mod thoughts;
|
||||||
|
mod users;
|
||||||
|
|
||||||
|
use axum::Router;
|
||||||
|
use utoipa::{
|
||||||
|
Modify, OpenApi,
|
||||||
|
openapi::security::{ApiKey, ApiKeyValue, Http, HttpAuthScheme, SecurityScheme},
|
||||||
|
};
|
||||||
|
use utoipa_scalar::{Scalar, Servable};
|
||||||
|
use utoipa_swagger_ui::SwaggerUi;
|
||||||
|
|
||||||
|
struct SecurityAddon;
|
||||||
|
|
||||||
|
impl Modify for SecurityAddon {
|
||||||
|
fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) {
|
||||||
|
let components = openapi.components.get_or_insert_with(Default::default);
|
||||||
|
components.add_security_scheme(
|
||||||
|
"bearer_auth",
|
||||||
|
SecurityScheme::Http(Http::new(HttpAuthScheme::Bearer)),
|
||||||
|
);
|
||||||
|
components.add_security_scheme(
|
||||||
|
"api_key",
|
||||||
|
SecurityScheme::ApiKey(ApiKey::Header(ApiKeyValue::new("X-Api-Key"))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build() -> utoipa::openapi::OpenApi {
|
||||||
|
let mut api = auth::AuthDoc::openapi();
|
||||||
|
api.info = utoipa::openapi::InfoBuilder::new()
|
||||||
|
.title("Thoughts API")
|
||||||
|
.version("2.0.0")
|
||||||
|
.description(Some(
|
||||||
|
"Federated social network API. Authenticate via `POST /auth/login` to get a Bearer token, \
|
||||||
|
or use `X-Api-Key` header with a key from `POST /api-keys`."
|
||||||
|
))
|
||||||
|
.build();
|
||||||
|
api.merge(users::UsersDoc::openapi());
|
||||||
|
api.merge(thoughts::ThoughtsDoc::openapi());
|
||||||
|
api.merge(feed::FeedDoc::openapi());
|
||||||
|
api.merge(social::SocialDoc::openapi());
|
||||||
|
api.merge(notifications::NotificationsDoc::openapi());
|
||||||
|
api.merge(api_keys::ApiKeysDoc::openapi());
|
||||||
|
api.merge(health::HealthDoc::openapi());
|
||||||
|
SecurityAddon.modify(&mut api);
|
||||||
|
api
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn serve<S: Clone + Send + Sync + 'static>(router: Router<S>) -> Router<S> {
|
||||||
|
tracing::info!("API docs at /docs (Swagger UI) and /scalar (Scalar)");
|
||||||
|
let spec = build();
|
||||||
|
router
|
||||||
|
.merge(SwaggerUi::new("/docs").url("/openapi.json", spec.clone()))
|
||||||
|
.merge(Scalar::with_url("/scalar", spec))
|
||||||
|
}
|
||||||
9
crates/presentation/src/openapi/notifications.rs
Normal file
9
crates/presentation/src/openapi/notifications.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
use utoipa::OpenApi;
|
||||||
|
|
||||||
|
#[derive(OpenApi)]
|
||||||
|
#[openapi(paths(
|
||||||
|
crate::handlers::notifications::list_notifications,
|
||||||
|
crate::handlers::notifications::mark_notification_read,
|
||||||
|
crate::handlers::notifications::mark_all_read,
|
||||||
|
))]
|
||||||
|
pub struct NotificationsDoc;
|
||||||
20
crates/presentation/src/openapi/social.rs
Normal file
20
crates/presentation/src/openapi/social.rs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
use utoipa::OpenApi;
|
||||||
|
use api_types::requests::SetTopFriendsRequest;
|
||||||
|
|
||||||
|
#[derive(OpenApi)]
|
||||||
|
#[openapi(
|
||||||
|
paths(
|
||||||
|
crate::handlers::social::post_like,
|
||||||
|
crate::handlers::social::delete_like,
|
||||||
|
crate::handlers::social::post_boost,
|
||||||
|
crate::handlers::social::delete_boost,
|
||||||
|
crate::handlers::social::post_follow,
|
||||||
|
crate::handlers::social::delete_follow,
|
||||||
|
crate::handlers::social::post_block,
|
||||||
|
crate::handlers::social::delete_block,
|
||||||
|
crate::handlers::social::put_top_friends,
|
||||||
|
crate::handlers::social::get_top_friends_handler,
|
||||||
|
),
|
||||||
|
components(schemas(SetTopFriendsRequest))
|
||||||
|
)]
|
||||||
|
pub struct SocialDoc;
|
||||||
15
crates/presentation/src/openapi/thoughts.rs
Normal file
15
crates/presentation/src/openapi/thoughts.rs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
use utoipa::OpenApi;
|
||||||
|
use api_types::{requests::{CreateThoughtRequest, EditThoughtRequest}, responses::ErrorResponse};
|
||||||
|
|
||||||
|
#[derive(OpenApi)]
|
||||||
|
#[openapi(
|
||||||
|
paths(
|
||||||
|
crate::handlers::thoughts::post_thought,
|
||||||
|
crate::handlers::thoughts::get_thought_handler,
|
||||||
|
crate::handlers::thoughts::patch_thought,
|
||||||
|
crate::handlers::thoughts::delete_thought_handler,
|
||||||
|
crate::handlers::thoughts::get_thread_handler,
|
||||||
|
),
|
||||||
|
components(schemas(CreateThoughtRequest, EditThoughtRequest, ErrorResponse))
|
||||||
|
)]
|
||||||
|
pub struct ThoughtsDoc;
|
||||||
13
crates/presentation/src/openapi/users.rs
Normal file
13
crates/presentation/src/openapi/users.rs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
use utoipa::OpenApi;
|
||||||
|
use api_types::{requests::UpdateProfileRequest, responses::{UserResponse, ErrorResponse}};
|
||||||
|
|
||||||
|
#[derive(OpenApi)]
|
||||||
|
#[openapi(
|
||||||
|
paths(
|
||||||
|
crate::handlers::users::get_me,
|
||||||
|
crate::handlers::users::get_user,
|
||||||
|
crate::handlers::users::patch_profile,
|
||||||
|
),
|
||||||
|
components(schemas(UserResponse, UpdateProfileRequest, ErrorResponse))
|
||||||
|
)]
|
||||||
|
pub struct UsersDoc;
|
||||||
@@ -12,7 +12,7 @@ use activitypub_base::{
|
|||||||
ApFederationConfig,
|
ApFederationConfig,
|
||||||
};
|
};
|
||||||
use activitypub_federation::config::FederationMiddleware;
|
use activitypub_federation::config::FederationMiddleware;
|
||||||
use crate::{handlers::*, state::AppState};
|
use crate::{handlers::*, openapi, state::AppState};
|
||||||
|
|
||||||
pub fn router(fed_config: &ApFederationConfig) -> Router<AppState> {
|
pub fn router(fed_config: &ApFederationConfig) -> Router<AppState> {
|
||||||
let api_routes = Router::new()
|
let api_routes = Router::new()
|
||||||
@@ -79,8 +79,10 @@ pub fn router(fed_config: &ApFederationConfig) -> Router<AppState> {
|
|||||||
.route("/users/{username}/followers", get(followers_handler))
|
.route("/users/{username}/followers", get(followers_handler))
|
||||||
.route("/users/{username}/following", get(following_handler));
|
.route("/users/{username}/following", get(following_handler));
|
||||||
|
|
||||||
Router::new()
|
let combined = Router::new()
|
||||||
.merge(api_routes)
|
.merge(api_routes)
|
||||||
.merge(ap_routes)
|
.merge(ap_routes)
|
||||||
.layer(FederationMiddleware::new(fed_config.0.clone()))
|
.layer(FederationMiddleware::new(fed_config.0.clone()));
|
||||||
|
|
||||||
|
openapi::serve(combined)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user