189 lines
5.7 KiB
Rust
189 lines
5.7 KiB
Rust
use crate::{
|
|
errors::ApiError,
|
|
extractors::{AuthUser, OptionalAuthUser},
|
|
handlers::auth::to_user_response,
|
|
state::AppState,
|
|
};
|
|
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 axum::{
|
|
extract::{Path, State},
|
|
http::StatusCode,
|
|
response::IntoResponse,
|
|
Json,
|
|
};
|
|
use domain::value_objects::ThoughtId;
|
|
use uuid::Uuid;
|
|
|
|
fn visibility_as_str(v: &domain::models::thought::Visibility) -> &'static str {
|
|
use domain::models::thought::Visibility;
|
|
match v {
|
|
Visibility::Public => "public",
|
|
Visibility::Followers => "followers",
|
|
Visibility::Unlisted => "unlisted",
|
|
Visibility::Direct => "direct",
|
|
}
|
|
}
|
|
|
|
fn thought_to_json(
|
|
t: &domain::models::thought::Thought,
|
|
author: &domain::models::user::User,
|
|
like_count: i64,
|
|
boost_count: i64,
|
|
reply_count: i64,
|
|
) -> serde_json::Value {
|
|
serde_json::json!({
|
|
"id": t.id.as_uuid(),
|
|
"content": t.content.as_str(),
|
|
"author": to_user_response(author),
|
|
"replyToId": t.in_reply_to_id.as_ref().map(|x| x.as_uuid()),
|
|
"visibility": visibility_as_str(&t.visibility),
|
|
"contentWarning": t.content_warning,
|
|
"sensitive": t.sensitive,
|
|
"likeCount": like_count,
|
|
"boostCount": boost_count,
|
|
"replyCount": reply_count,
|
|
"likedByViewer": false,
|
|
"boostedByViewer": false,
|
|
"createdAt": t.created_at,
|
|
"updatedAt": t.updated_at,
|
|
})
|
|
}
|
|
|
|
#[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.tags,
|
|
&*s.events,
|
|
CreateThoughtInput {
|
|
user_id: uid.clone(),
|
|
content: body.content,
|
|
in_reply_to_id: in_reply_to,
|
|
visibility: body.visibility,
|
|
content_warning: body.content_warning,
|
|
sensitive: body.sensitive.unwrap_or(false),
|
|
},
|
|
)
|
|
.await?;
|
|
let author = s
|
|
.users
|
|
.find_by_id(&uid)
|
|
.await?
|
|
.ok_or(domain::errors::DomainError::NotFound)?;
|
|
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();
|
|
for t in &thoughts {
|
|
if let Ok(Some(author)) = s.users.find_by_id(&t.user_id).await {
|
|
items.push(thought_to_json(t, &author, 0, 0, 0));
|
|
}
|
|
}
|
|
Ok(Json(items))
|
|
}
|