feat(presentation): add utoipa::path annotations to all handlers

This commit is contained in:
2026-05-14 11:34:02 +02:00
parent 4f990afe5e
commit 137d1a0c6a
8 changed files with 141 additions and 4 deletions

View File

@@ -1,6 +1,6 @@
use axum::{extract::{Path, State}, http::StatusCode, response::IntoResponse, Json};
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 domain::value_objects::ThoughtId;
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> {
let in_reply_to = body.in_reply_to_id.map(ThoughtId::from_uuid);
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))))
}
#[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> {
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)?;
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> {
delete_thought(&*s.thoughts, &*s.events, &ThoughtId::from_uuid(id), &uid).await?;
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> {
edit_thought(&*s.thoughts, &*s.events, &ThoughtId::from_uuid(id), &uid, body.content).await?;
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> {
let thoughts = get_thread(&*s.thoughts, &ThoughtId::from_uuid(id)).await?;
let mut items = Vec::new();