feat(presentation): add utoipa::path annotations to all handlers
This commit is contained in:
@@ -1,18 +1,21 @@
|
|||||||
use axum::{extract::{Path, State}, http::StatusCode, Json};
|
use axum::{extract::{Path, State}, http::StatusCode, Json};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use api_types::{requests::CreateApiKeyRequest, responses::ApiKeyResponse};
|
use api_types::{requests::CreateApiKeyRequest, responses::{ApiKeyResponse, CreatedApiKeyResponse}};
|
||||||
use application::use_cases::api_keys::{create_api_key, delete_api_key, list_api_keys};
|
use application::use_cases::api_keys::{create_api_key, delete_api_key, list_api_keys};
|
||||||
use domain::value_objects::ApiKeyId;
|
use domain::value_objects::ApiKeyId;
|
||||||
use crate::{errors::ApiError, extractors::AuthUser, state::AppState};
|
use crate::{errors::ApiError, extractors::AuthUser, state::AppState};
|
||||||
|
|
||||||
|
#[utoipa::path(get, path = "/api-keys", responses((status = 200, description = "API keys", body = Vec<ApiKeyResponse>)), security(("bearer_auth" = [])))]
|
||||||
pub async fn get_api_keys(State(s): State<AppState>, AuthUser(uid): AuthUser) -> Result<Json<Vec<ApiKeyResponse>>, ApiError> {
|
pub async fn get_api_keys(State(s): State<AppState>, AuthUser(uid): AuthUser) -> Result<Json<Vec<ApiKeyResponse>>, ApiError> {
|
||||||
let keys = list_api_keys(&*s.api_keys, &uid).await?;
|
let keys = list_api_keys(&*s.api_keys, &uid).await?;
|
||||||
Ok(Json(keys.into_iter().map(|k| ApiKeyResponse { id: k.id.as_uuid(), name: k.name, created_at: k.created_at }).collect()))
|
Ok(Json(keys.into_iter().map(|k| ApiKeyResponse { id: k.id.as_uuid(), name: k.name, created_at: k.created_at }).collect()))
|
||||||
}
|
}
|
||||||
|
#[utoipa::path(post, path = "/api-keys", request_body = CreateApiKeyRequest, responses((status = 200, description = "Created — raw key shown once", body = CreatedApiKeyResponse)), security(("bearer_auth" = [])))]
|
||||||
pub async fn post_api_key(State(s): State<AppState>, AuthUser(uid): AuthUser, Json(body): Json<CreateApiKeyRequest>) -> Result<Json<serde_json::Value>, ApiError> {
|
pub async fn post_api_key(State(s): State<AppState>, AuthUser(uid): AuthUser, Json(body): Json<CreateApiKeyRequest>) -> Result<Json<serde_json::Value>, ApiError> {
|
||||||
let (key, raw) = create_api_key(&*s.api_keys, &uid, body.name).await?;
|
let (key, raw) = create_api_key(&*s.api_keys, &uid, body.name).await?;
|
||||||
Ok(Json(serde_json::json!({ "id": key.id.as_uuid(), "name": key.name, "key": raw })))
|
Ok(Json(serde_json::json!({ "id": key.id.as_uuid(), "name": key.name, "key": raw })))
|
||||||
}
|
}
|
||||||
|
#[utoipa::path(delete, path = "/api-keys/{id}", params(("id" = uuid::Uuid, Path, description = "Key ID")), responses((status = 204, description = "Deleted")), security(("bearer_auth" = [])))]
|
||||||
pub async fn delete_api_key_handler(State(s): State<AppState>, AuthUser(uid): AuthUser, Path(id): Path<Uuid>) -> Result<StatusCode, ApiError> {
|
pub async fn delete_api_key_handler(State(s): State<AppState>, AuthUser(uid): AuthUser, Path(id): Path<Uuid>) -> Result<StatusCode, ApiError> {
|
||||||
delete_api_key(&*s.api_keys, &uid, &ApiKeyId::from_uuid(id)).await?;
|
delete_api_key(&*s.api_keys, &uid, &ApiKeyId::from_uuid(id)).await?;
|
||||||
Ok(StatusCode::NO_CONTENT)
|
Ok(StatusCode::NO_CONTENT)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use axum::{extract::State, http::StatusCode, response::IntoResponse, Json};
|
use axum::{extract::State, http::StatusCode, response::IntoResponse, Json};
|
||||||
use api_types::{requests::{LoginRequest, RegisterRequest}, responses::{AuthResponse, UserResponse}};
|
use api_types::{requests::{LoginRequest, RegisterRequest}, responses::{AuthResponse, ErrorResponse, UserResponse}};
|
||||||
use application::use_cases::auth::{login, register, LoginInput, RegisterInput};
|
use application::use_cases::auth::{login, register, LoginInput, RegisterInput};
|
||||||
use crate::{errors::ApiError, state::AppState};
|
use crate::{errors::ApiError, state::AppState};
|
||||||
|
|
||||||
@@ -16,6 +16,15 @@ pub fn to_user_response(u: &domain::models::user::User) -> UserResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
post, path = "/auth/register",
|
||||||
|
request_body = RegisterRequest,
|
||||||
|
responses(
|
||||||
|
(status = 201, description = "User registered", body = AuthResponse),
|
||||||
|
(status = 409, description = "Username or email taken", body = ErrorResponse),
|
||||||
|
(status = 422, description = "Invalid input", body = ErrorResponse),
|
||||||
|
)
|
||||||
|
)]
|
||||||
pub async fn post_register(State(s): State<AppState>, Json(body): Json<RegisterRequest>) -> Result<impl IntoResponse, ApiError> {
|
pub async fn post_register(State(s): State<AppState>, Json(body): Json<RegisterRequest>) -> Result<impl IntoResponse, ApiError> {
|
||||||
let out = register(&*s.users, &*s.hasher, &*s.auth, &*s.events, RegisterInput {
|
let out = register(&*s.users, &*s.hasher, &*s.auth, &*s.events, RegisterInput {
|
||||||
username: body.username,
|
username: body.username,
|
||||||
@@ -26,6 +35,14 @@ pub async fn post_register(State(s): State<AppState>, Json(body): Json<RegisterR
|
|||||||
Ok((StatusCode::CREATED, Json(resp)))
|
Ok((StatusCode::CREATED, Json(resp)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
post, path = "/auth/login",
|
||||||
|
request_body = LoginRequest,
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "Login successful", body = AuthResponse),
|
||||||
|
(status = 401, description = "Invalid credentials", body = ErrorResponse),
|
||||||
|
)
|
||||||
|
)]
|
||||||
pub async fn post_login(State(s): State<AppState>, Json(body): Json<LoginRequest>) -> Result<impl IntoResponse, ApiError> {
|
pub async fn post_login(State(s): State<AppState>, Json(body): Json<LoginRequest>) -> Result<impl IntoResponse, ApiError> {
|
||||||
let out = login(&*s.users, &*s.hasher, &*s.auth, LoginInput {
|
let out = login(&*s.users, &*s.hasher, &*s.auth, LoginInput {
|
||||||
email: body.email,
|
email: body.email,
|
||||||
|
|||||||
@@ -5,18 +5,34 @@ use domain::models::feed::PageParams;
|
|||||||
use crate::{errors::ApiError, extractors::{AuthUser, OptionalAuthUser}, handlers::auth::to_user_response, state::AppState};
|
use crate::{errors::ApiError, extractors::{AuthUser, OptionalAuthUser}, handlers::auth::to_user_response, state::AppState};
|
||||||
use application::use_cases::profile::get_user_by_username;
|
use application::use_cases::profile::get_user_by_username;
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
get, path = "/feed",
|
||||||
|
params(PaginationQuery),
|
||||||
|
responses((status = 200, description = "Home feed")),
|
||||||
|
security(("bearer_auth" = []))
|
||||||
|
)]
|
||||||
pub async fn home_feed(State(s): State<AppState>, AuthUser(uid): AuthUser, Query(q): Query<PaginationQuery>) -> Result<Json<serde_json::Value>, ApiError> {
|
pub async fn home_feed(State(s): State<AppState>, AuthUser(uid): AuthUser, Query(q): Query<PaginationQuery>) -> Result<Json<serde_json::Value>, ApiError> {
|
||||||
let page = PageParams { page: q.page(), per_page: q.per_page() };
|
let page = PageParams { page: q.page(), per_page: q.per_page() };
|
||||||
let result = get_home_feed(&*s.feed, &*s.follows, &uid, page).await?;
|
let result = get_home_feed(&*s.feed, &*s.follows, &uid, page).await?;
|
||||||
Ok(Json(serde_json::json!({ "items": result.items.iter().map(|e| e.thought.id.as_uuid()).collect::<Vec<_>>(), "total": result.total, "page": result.page })))
|
Ok(Json(serde_json::json!({ "items": result.items.iter().map(|e| e.thought.id.as_uuid()).collect::<Vec<_>>(), "total": result.total, "page": result.page })))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
get, path = "/feed/public",
|
||||||
|
params(PaginationQuery),
|
||||||
|
responses((status = 200, description = "Public feed"))
|
||||||
|
)]
|
||||||
pub async fn public_feed(State(s): State<AppState>, OptionalAuthUser(viewer): OptionalAuthUser, Query(q): Query<PaginationQuery>) -> Result<Json<serde_json::Value>, ApiError> {
|
pub async fn public_feed(State(s): State<AppState>, OptionalAuthUser(viewer): OptionalAuthUser, Query(q): Query<PaginationQuery>) -> Result<Json<serde_json::Value>, ApiError> {
|
||||||
let page = PageParams { page: q.page(), per_page: q.per_page() };
|
let page = PageParams { page: q.page(), per_page: q.per_page() };
|
||||||
let result = get_public_feed(&*s.feed, viewer.as_ref(), page).await?;
|
let result = get_public_feed(&*s.feed, viewer.as_ref(), page).await?;
|
||||||
Ok(Json(serde_json::json!({ "items": result.items.iter().map(|e| e.thought.id.as_uuid()).collect::<Vec<_>>(), "total": result.total, "page": result.page })))
|
Ok(Json(serde_json::json!({ "items": result.items.iter().map(|e| e.thought.id.as_uuid()).collect::<Vec<_>>(), "total": result.total, "page": result.page })))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
get, path = "/search",
|
||||||
|
params(SearchQuery),
|
||||||
|
responses((status = 200, description = "Search results: thoughts and users"))
|
||||||
|
)]
|
||||||
pub async fn search_handler(
|
pub async fn search_handler(
|
||||||
State(s): State<AppState>,
|
State(s): State<AppState>,
|
||||||
OptionalAuthUser(viewer): OptionalAuthUser,
|
OptionalAuthUser(viewer): OptionalAuthUser,
|
||||||
@@ -63,6 +79,14 @@ pub async fn get_followers_handler(State(s): State<AppState>, Path(username): Pa
|
|||||||
Ok(Json(serde_json::json!({ "total": result.total, "items": result.items.iter().map(to_user_response).collect::<Vec<_>>() })))
|
Ok(Json(serde_json::json!({ "total": result.total, "items": result.items.iter().map(to_user_response).collect::<Vec<_>>() })))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
get, path = "/users/{username}/thoughts",
|
||||||
|
params(
|
||||||
|
("username" = String, Path, description = "Username"),
|
||||||
|
PaginationQuery,
|
||||||
|
),
|
||||||
|
responses((status = 200, description = "User's public thoughts"))
|
||||||
|
)]
|
||||||
pub async fn user_thoughts_handler(
|
pub async fn user_thoughts_handler(
|
||||||
State(s): State<AppState>,
|
State(s): State<AppState>,
|
||||||
Path(username): Path<String>,
|
Path(username): Path<String>,
|
||||||
@@ -88,6 +112,14 @@ pub async fn user_thoughts_handler(
|
|||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
get, path = "/tags/{name}",
|
||||||
|
params(
|
||||||
|
("name" = String, Path, description = "Tag name"),
|
||||||
|
PaginationQuery,
|
||||||
|
),
|
||||||
|
responses((status = 200, description = "Thoughts with this tag"))
|
||||||
|
)]
|
||||||
pub async fn tag_thoughts_handler(
|
pub async fn tag_thoughts_handler(
|
||||||
State(s): State<AppState>,
|
State(s): State<AppState>,
|
||||||
Path(tag_name): Path<String>,
|
Path(tag_name): Path<String>,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use axum::{extract::State, Json};
|
use axum::{extract::State, Json};
|
||||||
use crate::state::AppState;
|
use crate::state::AppState;
|
||||||
|
|
||||||
|
#[utoipa::path(get, path = "/health", responses((status = 200, description = "Service health status")))]
|
||||||
pub async fn health_handler(State(s): State<AppState>) -> Json<serde_json::Value> {
|
pub async fn health_handler(State(s): State<AppState>) -> Json<serde_json::Value> {
|
||||||
let db_ok = s.users.list_with_stats().await.is_ok();
|
let db_ok = s.users.list_with_stats().await.is_ok();
|
||||||
Json(serde_json::json!({
|
Json(serde_json::json!({
|
||||||
|
|||||||
@@ -3,15 +3,18 @@ use uuid::Uuid;
|
|||||||
use domain::{models::feed::PageParams, value_objects::NotificationId};
|
use domain::{models::feed::PageParams, value_objects::NotificationId};
|
||||||
use crate::{errors::ApiError, extractors::AuthUser, state::AppState};
|
use crate::{errors::ApiError, extractors::AuthUser, state::AppState};
|
||||||
|
|
||||||
|
#[utoipa::path(get, path = "/notifications", responses((status = 200, description = "Notification summary")), security(("bearer_auth" = [])))]
|
||||||
pub async fn list_notifications(State(s): State<AppState>, AuthUser(uid): AuthUser) -> Result<Json<serde_json::Value>, ApiError> {
|
pub async fn list_notifications(State(s): State<AppState>, AuthUser(uid): AuthUser) -> Result<Json<serde_json::Value>, ApiError> {
|
||||||
let page = PageParams { page: 1, per_page: 20 };
|
let page = PageParams { page: 1, per_page: 20 };
|
||||||
let result = s.notifications.list_for_user(&uid, &page).await?;
|
let result = s.notifications.list_for_user(&uid, &page).await?;
|
||||||
Ok(Json(serde_json::json!({ "total": result.total, "unread": result.items.iter().filter(|n| !n.read).count() })))
|
Ok(Json(serde_json::json!({ "total": result.total, "unread": result.items.iter().filter(|n| !n.read).count() })))
|
||||||
}
|
}
|
||||||
|
#[utoipa::path(post, path = "/notifications/{id}/read", params(("id" = uuid::Uuid, Path, description = "Notification ID")), responses((status = 204, description = "Marked read")), security(("bearer_auth" = [])))]
|
||||||
pub async fn mark_notification_read(State(s): State<AppState>, AuthUser(uid): AuthUser, Path(id): Path<Uuid>) -> Result<StatusCode, ApiError> {
|
pub async fn mark_notification_read(State(s): State<AppState>, AuthUser(uid): AuthUser, Path(id): Path<Uuid>) -> Result<StatusCode, ApiError> {
|
||||||
s.notifications.mark_read(&NotificationId::from_uuid(id), &uid).await?;
|
s.notifications.mark_read(&NotificationId::from_uuid(id), &uid).await?;
|
||||||
Ok(StatusCode::NO_CONTENT)
|
Ok(StatusCode::NO_CONTENT)
|
||||||
}
|
}
|
||||||
|
#[utoipa::path(post, path = "/notifications/read-all", responses((status = 204, description = "All marked read")), security(("bearer_auth" = [])))]
|
||||||
pub async fn mark_all_read(State(s): State<AppState>, AuthUser(uid): AuthUser) -> Result<StatusCode, ApiError> {
|
pub async fn mark_all_read(State(s): State<AppState>, AuthUser(uid): AuthUser) -> Result<StatusCode, ApiError> {
|
||||||
s.notifications.mark_all_read(&uid).await?;
|
s.notifications.mark_all_read(&uid).await?;
|
||||||
Ok(StatusCode::NO_CONTENT)
|
Ok(StatusCode::NO_CONTENT)
|
||||||
|
|||||||
@@ -6,43 +6,53 @@ use application::use_cases::profile::{get_top_friends, set_top_friends, get_user
|
|||||||
use domain::value_objects::{ThoughtId, UserId};
|
use domain::value_objects::{ThoughtId, UserId};
|
||||||
use crate::{errors::ApiError, extractors::AuthUser, state::AppState};
|
use crate::{errors::ApiError, extractors::AuthUser, state::AppState};
|
||||||
|
|
||||||
|
#[utoipa::path(post, path = "/thoughts/{id}/like", params(("id" = uuid::Uuid, Path, description = "Thought ID")), responses((status = 204, description = "Liked")), security(("bearer_auth" = [])))]
|
||||||
pub async fn post_like(State(s): State<AppState>, AuthUser(uid): AuthUser, Path(id): Path<Uuid>) -> Result<StatusCode, ApiError> {
|
pub async fn post_like(State(s): State<AppState>, AuthUser(uid): AuthUser, Path(id): Path<Uuid>) -> Result<StatusCode, ApiError> {
|
||||||
like_thought(&*s.likes, &*s.events, &uid, &ThoughtId::from_uuid(id)).await?;
|
like_thought(&*s.likes, &*s.events, &uid, &ThoughtId::from_uuid(id)).await?;
|
||||||
Ok(StatusCode::NO_CONTENT)
|
Ok(StatusCode::NO_CONTENT)
|
||||||
}
|
}
|
||||||
|
#[utoipa::path(delete, path = "/thoughts/{id}/like", params(("id" = uuid::Uuid, Path, description = "Thought ID")), responses((status = 204, description = "Unliked")), security(("bearer_auth" = [])))]
|
||||||
pub async fn delete_like(State(s): State<AppState>, AuthUser(uid): AuthUser, Path(id): Path<Uuid>) -> Result<StatusCode, ApiError> {
|
pub async fn delete_like(State(s): State<AppState>, AuthUser(uid): AuthUser, Path(id): Path<Uuid>) -> Result<StatusCode, ApiError> {
|
||||||
unlike_thought(&*s.likes, &*s.events, &uid, &ThoughtId::from_uuid(id)).await?;
|
unlike_thought(&*s.likes, &*s.events, &uid, &ThoughtId::from_uuid(id)).await?;
|
||||||
Ok(StatusCode::NO_CONTENT)
|
Ok(StatusCode::NO_CONTENT)
|
||||||
}
|
}
|
||||||
|
#[utoipa::path(post, path = "/thoughts/{id}/boost", params(("id" = uuid::Uuid, Path, description = "Thought ID")), responses((status = 204, description = "Boosted")), security(("bearer_auth" = [])))]
|
||||||
pub async fn post_boost(State(s): State<AppState>, AuthUser(uid): AuthUser, Path(id): Path<Uuid>) -> Result<StatusCode, ApiError> {
|
pub async fn post_boost(State(s): State<AppState>, AuthUser(uid): AuthUser, Path(id): Path<Uuid>) -> Result<StatusCode, ApiError> {
|
||||||
boost_thought(&*s.boosts, &*s.events, &uid, &ThoughtId::from_uuid(id)).await?;
|
boost_thought(&*s.boosts, &*s.events, &uid, &ThoughtId::from_uuid(id)).await?;
|
||||||
Ok(StatusCode::NO_CONTENT)
|
Ok(StatusCode::NO_CONTENT)
|
||||||
}
|
}
|
||||||
|
#[utoipa::path(delete, path = "/thoughts/{id}/boost", params(("id" = uuid::Uuid, Path, description = "Thought ID")), responses((status = 204, description = "Unboosted")), security(("bearer_auth" = [])))]
|
||||||
pub async fn delete_boost(State(s): State<AppState>, AuthUser(uid): AuthUser, Path(id): Path<Uuid>) -> Result<StatusCode, ApiError> {
|
pub async fn delete_boost(State(s): State<AppState>, AuthUser(uid): AuthUser, Path(id): Path<Uuid>) -> Result<StatusCode, ApiError> {
|
||||||
unboost_thought(&*s.boosts, &*s.events, &uid, &ThoughtId::from_uuid(id)).await?;
|
unboost_thought(&*s.boosts, &*s.events, &uid, &ThoughtId::from_uuid(id)).await?;
|
||||||
Ok(StatusCode::NO_CONTENT)
|
Ok(StatusCode::NO_CONTENT)
|
||||||
}
|
}
|
||||||
|
#[utoipa::path(post, path = "/users/{id}/follow", params(("id" = uuid::Uuid, Path, description = "User ID")), responses((status = 204, description = "Following")), security(("bearer_auth" = [])))]
|
||||||
pub async fn post_follow(State(s): State<AppState>, AuthUser(uid): AuthUser, Path(target): Path<Uuid>) -> Result<StatusCode, ApiError> {
|
pub async fn post_follow(State(s): State<AppState>, AuthUser(uid): AuthUser, Path(target): Path<Uuid>) -> Result<StatusCode, ApiError> {
|
||||||
follow_user(&*s.follows, &*s.events, &uid, &UserId::from_uuid(target)).await?;
|
follow_user(&*s.follows, &*s.events, &uid, &UserId::from_uuid(target)).await?;
|
||||||
Ok(StatusCode::NO_CONTENT)
|
Ok(StatusCode::NO_CONTENT)
|
||||||
}
|
}
|
||||||
|
#[utoipa::path(delete, path = "/users/{id}/follow", params(("id" = uuid::Uuid, Path, description = "User ID")), responses((status = 204, description = "Unfollowed")), security(("bearer_auth" = [])))]
|
||||||
pub async fn delete_follow(State(s): State<AppState>, AuthUser(uid): AuthUser, Path(target): Path<Uuid>) -> Result<StatusCode, ApiError> {
|
pub async fn delete_follow(State(s): State<AppState>, AuthUser(uid): AuthUser, Path(target): Path<Uuid>) -> Result<StatusCode, ApiError> {
|
||||||
unfollow_user(&*s.follows, &*s.events, &uid, &UserId::from_uuid(target)).await?;
|
unfollow_user(&*s.follows, &*s.events, &uid, &UserId::from_uuid(target)).await?;
|
||||||
Ok(StatusCode::NO_CONTENT)
|
Ok(StatusCode::NO_CONTENT)
|
||||||
}
|
}
|
||||||
|
#[utoipa::path(post, path = "/users/{id}/block", params(("id" = uuid::Uuid, Path, description = "User ID")), responses((status = 204, description = "Blocked")), security(("bearer_auth" = [])))]
|
||||||
pub async fn post_block(State(s): State<AppState>, AuthUser(uid): AuthUser, Path(target): Path<Uuid>) -> Result<StatusCode, ApiError> {
|
pub async fn post_block(State(s): State<AppState>, AuthUser(uid): AuthUser, Path(target): Path<Uuid>) -> Result<StatusCode, ApiError> {
|
||||||
block_user(&*s.blocks, &*s.events, &uid, &UserId::from_uuid(target)).await?;
|
block_user(&*s.blocks, &*s.events, &uid, &UserId::from_uuid(target)).await?;
|
||||||
Ok(StatusCode::NO_CONTENT)
|
Ok(StatusCode::NO_CONTENT)
|
||||||
}
|
}
|
||||||
|
#[utoipa::path(delete, path = "/users/{id}/block", params(("id" = uuid::Uuid, Path, description = "User ID")), responses((status = 204, description = "Unblocked")), security(("bearer_auth" = [])))]
|
||||||
pub async fn delete_block(State(s): State<AppState>, AuthUser(uid): AuthUser, Path(target): Path<Uuid>) -> Result<StatusCode, ApiError> {
|
pub async fn delete_block(State(s): State<AppState>, AuthUser(uid): AuthUser, Path(target): Path<Uuid>) -> Result<StatusCode, ApiError> {
|
||||||
unblock_user(&*s.blocks, &*s.events, &uid, &UserId::from_uuid(target)).await?;
|
unblock_user(&*s.blocks, &*s.events, &uid, &UserId::from_uuid(target)).await?;
|
||||||
Ok(StatusCode::NO_CONTENT)
|
Ok(StatusCode::NO_CONTENT)
|
||||||
}
|
}
|
||||||
|
#[utoipa::path(put, path = "/users/me/top-friends", request_body = SetTopFriendsRequest, responses((status = 204, description = "Top friends updated")), security(("bearer_auth" = [])))]
|
||||||
pub async fn put_top_friends(State(s): State<AppState>, AuthUser(uid): AuthUser, Json(body): Json<SetTopFriendsRequest>) -> Result<StatusCode, ApiError> {
|
pub async fn put_top_friends(State(s): State<AppState>, AuthUser(uid): AuthUser, Json(body): Json<SetTopFriendsRequest>) -> Result<StatusCode, ApiError> {
|
||||||
let ids: Vec<UserId> = body.friend_ids.into_iter().map(UserId::from_uuid).collect();
|
let ids: Vec<UserId> = body.friend_ids.into_iter().map(UserId::from_uuid).collect();
|
||||||
set_top_friends(&*s.top_friends, &uid, ids).await?;
|
set_top_friends(&*s.top_friends, &uid, ids).await?;
|
||||||
Ok(StatusCode::NO_CONTENT)
|
Ok(StatusCode::NO_CONTENT)
|
||||||
}
|
}
|
||||||
|
#[utoipa::path(get, path = "/users/{username}/top-friends", params(("username" = String, Path, description = "Username")), responses((status = 200, description = "Top friends list")))]
|
||||||
pub async fn get_top_friends_handler(State(s): State<AppState>, Path(username): Path<String>) -> Result<Json<serde_json::Value>, ApiError> {
|
pub async fn get_top_friends_handler(State(s): State<AppState>, Path(username): Path<String>) -> Result<Json<serde_json::Value>, ApiError> {
|
||||||
let user = get_user_by_username(&*s.users, &username).await?;
|
let user = get_user_by_username(&*s.users, &username).await?;
|
||||||
let friends = get_top_friends(&*s.top_friends, &user.id).await?;
|
let friends = get_top_friends(&*s.top_friends, &user.id).await?;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use axum::{extract::{Path, State}, http::StatusCode, response::IntoResponse, Json};
|
use axum::{extract::{Path, State}, http::StatusCode, response::IntoResponse, Json};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use api_types::requests::{CreateThoughtRequest, EditThoughtRequest};
|
use api_types::{requests::{CreateThoughtRequest, EditThoughtRequest}, responses::ErrorResponse};
|
||||||
use application::use_cases::thoughts::{create_thought, delete_thought, edit_thought, get_thought, get_thread, CreateThoughtInput};
|
use application::use_cases::thoughts::{create_thought, delete_thought, edit_thought, get_thought, get_thread, CreateThoughtInput};
|
||||||
use domain::value_objects::ThoughtId;
|
use domain::value_objects::ThoughtId;
|
||||||
use crate::{errors::ApiError, extractors::{AuthUser, OptionalAuthUser}, handlers::auth::to_user_response, state::AppState};
|
use crate::{errors::ApiError, extractors::{AuthUser, OptionalAuthUser}, handlers::auth::to_user_response, state::AppState};
|
||||||
@@ -22,6 +22,16 @@ fn thought_to_json(t: &domain::models::thought::Thought, author: &domain::models
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
post, path = "/thoughts",
|
||||||
|
request_body = CreateThoughtRequest,
|
||||||
|
responses(
|
||||||
|
(status = 201, description = "Thought created"),
|
||||||
|
(status = 401, description = "Unauthorized", body = ErrorResponse),
|
||||||
|
(status = 422, description = "Content too long", body = ErrorResponse),
|
||||||
|
),
|
||||||
|
security(("bearer_auth" = []))
|
||||||
|
)]
|
||||||
pub async fn post_thought(State(s): State<AppState>, AuthUser(uid): AuthUser, Json(body): Json<CreateThoughtRequest>) -> Result<impl IntoResponse, ApiError> {
|
pub async fn post_thought(State(s): State<AppState>, AuthUser(uid): AuthUser, Json(body): Json<CreateThoughtRequest>) -> Result<impl IntoResponse, ApiError> {
|
||||||
let in_reply_to = body.in_reply_to_id.map(ThoughtId::from_uuid);
|
let in_reply_to = body.in_reply_to_id.map(ThoughtId::from_uuid);
|
||||||
let out = create_thought(&*s.thoughts, &*s.users, &*s.events, CreateThoughtInput {
|
let out = create_thought(&*s.thoughts, &*s.users, &*s.events, CreateThoughtInput {
|
||||||
@@ -36,22 +46,58 @@ pub async fn post_thought(State(s): State<AppState>, AuthUser(uid): AuthUser, Js
|
|||||||
Ok((StatusCode::CREATED, Json(thought_to_json(&out.thought, &author, 0, 0, 0))))
|
Ok((StatusCode::CREATED, Json(thought_to_json(&out.thought, &author, 0, 0, 0))))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
get, path = "/thoughts/{id}",
|
||||||
|
params(("id" = uuid::Uuid, Path, description = "Thought ID")),
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "Thought with author info"),
|
||||||
|
(status = 404, description = "Not found", body = ErrorResponse),
|
||||||
|
)
|
||||||
|
)]
|
||||||
pub async fn get_thought_handler(State(s): State<AppState>, Path(id): Path<Uuid>, OptionalAuthUser(_viewer): OptionalAuthUser) -> Result<Json<serde_json::Value>, ApiError> {
|
pub async fn get_thought_handler(State(s): State<AppState>, Path(id): Path<Uuid>, OptionalAuthUser(_viewer): OptionalAuthUser) -> Result<Json<serde_json::Value>, ApiError> {
|
||||||
let thought = get_thought(&*s.thoughts, &ThoughtId::from_uuid(id)).await?;
|
let thought = get_thought(&*s.thoughts, &ThoughtId::from_uuid(id)).await?;
|
||||||
let author = s.users.find_by_id(&thought.user_id).await?.ok_or(domain::errors::DomainError::NotFound)?;
|
let author = s.users.find_by_id(&thought.user_id).await?.ok_or(domain::errors::DomainError::NotFound)?;
|
||||||
Ok(Json(thought_to_json(&thought, &author, 0, 0, 0)))
|
Ok(Json(thought_to_json(&thought, &author, 0, 0, 0)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
delete, path = "/thoughts/{id}",
|
||||||
|
params(("id" = uuid::Uuid, Path, description = "Thought ID")),
|
||||||
|
responses(
|
||||||
|
(status = 204, description = "Deleted"),
|
||||||
|
(status = 401, description = "Unauthorized", body = ErrorResponse),
|
||||||
|
(status = 404, description = "Not found or not owner", body = ErrorResponse),
|
||||||
|
),
|
||||||
|
security(("bearer_auth" = []))
|
||||||
|
)]
|
||||||
pub async fn delete_thought_handler(State(s): State<AppState>, AuthUser(uid): AuthUser, Path(id): Path<Uuid>) -> Result<StatusCode, ApiError> {
|
pub async fn delete_thought_handler(State(s): State<AppState>, AuthUser(uid): AuthUser, Path(id): Path<Uuid>) -> Result<StatusCode, ApiError> {
|
||||||
delete_thought(&*s.thoughts, &*s.events, &ThoughtId::from_uuid(id), &uid).await?;
|
delete_thought(&*s.thoughts, &*s.events, &ThoughtId::from_uuid(id), &uid).await?;
|
||||||
Ok(StatusCode::NO_CONTENT)
|
Ok(StatusCode::NO_CONTENT)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
patch, path = "/thoughts/{id}",
|
||||||
|
params(("id" = uuid::Uuid, Path, description = "Thought ID")),
|
||||||
|
request_body = EditThoughtRequest,
|
||||||
|
responses(
|
||||||
|
(status = 204, description = "Updated"),
|
||||||
|
(status = 401, description = "Unauthorized", body = ErrorResponse),
|
||||||
|
(status = 404, description = "Not found or not owner", body = ErrorResponse),
|
||||||
|
),
|
||||||
|
security(("bearer_auth" = []))
|
||||||
|
)]
|
||||||
pub async fn patch_thought(State(s): State<AppState>, AuthUser(uid): AuthUser, Path(id): Path<Uuid>, Json(body): Json<EditThoughtRequest>) -> Result<StatusCode, ApiError> {
|
pub async fn patch_thought(State(s): State<AppState>, AuthUser(uid): AuthUser, Path(id): Path<Uuid>, Json(body): Json<EditThoughtRequest>) -> Result<StatusCode, ApiError> {
|
||||||
edit_thought(&*s.thoughts, &*s.events, &ThoughtId::from_uuid(id), &uid, body.content).await?;
|
edit_thought(&*s.thoughts, &*s.events, &ThoughtId::from_uuid(id), &uid, body.content).await?;
|
||||||
Ok(StatusCode::NO_CONTENT)
|
Ok(StatusCode::NO_CONTENT)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
get, path = "/thoughts/{id}/thread",
|
||||||
|
params(("id" = uuid::Uuid, Path, description = "Root thought ID")),
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "Thread (root + replies)"),
|
||||||
|
)
|
||||||
|
)]
|
||||||
pub async fn get_thread_handler(State(s): State<AppState>, Path(id): Path<Uuid>) -> Result<Json<Vec<serde_json::Value>>, ApiError> {
|
pub async fn get_thread_handler(State(s): State<AppState>, Path(id): Path<Uuid>) -> Result<Json<Vec<serde_json::Value>>, ApiError> {
|
||||||
let thoughts = get_thread(&*s.thoughts, &ThoughtId::from_uuid(id)).await?;
|
let thoughts = get_thread(&*s.thoughts, &ThoughtId::from_uuid(id)).await?;
|
||||||
let mut items = Vec::new();
|
let mut items = Vec::new();
|
||||||
|
|||||||
@@ -1,19 +1,44 @@
|
|||||||
use axum::{extract::{Path, State}, Json};
|
use axum::{extract::{Path, State}, Json};
|
||||||
use api_types::{requests::UpdateProfileRequest, responses::UserResponse};
|
use api_types::{requests::UpdateProfileRequest, responses::{ErrorResponse, UserResponse}};
|
||||||
use application::use_cases::profile::{get_user_by_username, update_profile};
|
use application::use_cases::profile::{get_user_by_username, update_profile};
|
||||||
use crate::{errors::ApiError, extractors::AuthUser, handlers::auth::to_user_response, state::AppState};
|
use crate::{errors::ApiError, extractors::AuthUser, handlers::auth::to_user_response, state::AppState};
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
get, path = "/users/{username}",
|
||||||
|
params(("username" = String, Path, description = "Username")),
|
||||||
|
responses(
|
||||||
|
(status = 200, body = UserResponse),
|
||||||
|
(status = 404, description = "User not found", body = ErrorResponse),
|
||||||
|
)
|
||||||
|
)]
|
||||||
pub async fn get_user(State(s): State<AppState>, Path(username): Path<String>) -> Result<Json<UserResponse>, ApiError> {
|
pub async fn get_user(State(s): State<AppState>, Path(username): Path<String>) -> Result<Json<UserResponse>, ApiError> {
|
||||||
let user = get_user_by_username(&*s.users, &username).await?;
|
let user = get_user_by_username(&*s.users, &username).await?;
|
||||||
Ok(Json(to_user_response(&user)))
|
Ok(Json(to_user_response(&user)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
patch, path = "/users/me",
|
||||||
|
request_body = UpdateProfileRequest,
|
||||||
|
responses(
|
||||||
|
(status = 200, body = UserResponse),
|
||||||
|
(status = 401, description = "Unauthorized", body = ErrorResponse),
|
||||||
|
),
|
||||||
|
security(("bearer_auth" = []))
|
||||||
|
)]
|
||||||
pub async fn patch_profile(State(s): State<AppState>, AuthUser(uid): AuthUser, Json(body): Json<UpdateProfileRequest>) -> Result<Json<UserResponse>, ApiError> {
|
pub async fn patch_profile(State(s): State<AppState>, AuthUser(uid): AuthUser, Json(body): Json<UpdateProfileRequest>) -> Result<Json<UserResponse>, ApiError> {
|
||||||
update_profile(&*s.users, &uid, body.display_name, body.bio, body.avatar_url, body.header_url, body.custom_css).await?;
|
update_profile(&*s.users, &uid, body.display_name, body.bio, body.avatar_url, body.header_url, body.custom_css).await?;
|
||||||
let user = s.users.find_by_id(&uid).await?.ok_or(domain::errors::DomainError::NotFound)?;
|
let user = s.users.find_by_id(&uid).await?.ok_or(domain::errors::DomainError::NotFound)?;
|
||||||
Ok(Json(to_user_response(&user)))
|
Ok(Json(to_user_response(&user)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
get, path = "/users/me",
|
||||||
|
responses(
|
||||||
|
(status = 200, body = UserResponse),
|
||||||
|
(status = 401, description = "Unauthorized", body = ErrorResponse),
|
||||||
|
),
|
||||||
|
security(("bearer_auth" = []))
|
||||||
|
)]
|
||||||
pub async fn get_me(State(s): State<AppState>, AuthUser(uid): AuthUser) -> Result<Json<UserResponse>, ApiError> {
|
pub async fn get_me(State(s): State<AppState>, AuthUser(uid): AuthUser) -> Result<Json<UserResponse>, ApiError> {
|
||||||
let user = s.users.find_by_id(&uid).await?.ok_or(domain::errors::DomainError::NotFound)?;
|
let user = s.users.find_by_id(&uid).await?.ok_or(domain::errors::DomainError::NotFound)?;
|
||||||
Ok(Json(to_user_response(&user)))
|
Ok(Json(to_user_response(&user)))
|
||||||
|
|||||||
Reference in New Issue
Block a user