Some checks failed
lint / lint (push) Has been cancelled
test / unit (push) Has been cancelled
test / integration (push) Has been cancelled
lint / lint (pull_request) Failing after 9m34s
test / unit (pull_request) Successful in 16m5s
test / integration (pull_request) Failing after 18m6s
173 lines
5.1 KiB
Rust
173 lines
5.1 KiB
Rust
use crate::{
|
|
errors::ApiError,
|
|
extractors::{AuthUser, OptionalAuthUser},
|
|
handlers::auth::to_user_response,
|
|
state::AppState,
|
|
};
|
|
use api_types::{
|
|
requests::{PaginationQuery, UpdateProfileRequest},
|
|
responses::{ErrorResponse, UserResponse},
|
|
};
|
|
use application::use_cases::feed::list_users;
|
|
use application::use_cases::profile::{get_user_by_username, update_profile};
|
|
use application::use_cases::search::search_users;
|
|
use axum::{
|
|
extract::{Path, Query, State},
|
|
Json,
|
|
};
|
|
|
|
#[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>,
|
|
OptionalAuthUser(viewer): OptionalAuthUser,
|
|
) -> Result<Json<UserResponse>, ApiError> {
|
|
let user = get_user_by_username(&*s.users, &username).await?;
|
|
let is_followed = if let Some(viewer_id) = viewer {
|
|
s.follows.find(&viewer_id, &user.id).await?.is_some()
|
|
} else {
|
|
false
|
|
};
|
|
let mut resp = to_user_response(&user);
|
|
resp.is_followed_by_viewer = is_followed;
|
|
Ok(Json(resp))
|
|
}
|
|
|
|
#[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> {
|
|
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)?;
|
|
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> {
|
|
let user = s
|
|
.users
|
|
.find_by_id(&uid)
|
|
.await?
|
|
.ok_or(domain::errors::DomainError::NotFound)?;
|
|
Ok(Json(to_user_response(&user)))
|
|
}
|
|
|
|
pub async fn get_me_following_list(
|
|
State(s): State<AppState>,
|
|
AuthUser(uid): AuthUser,
|
|
Query(q): Query<PaginationQuery>,
|
|
) -> Result<Json<serde_json::Value>, ApiError> {
|
|
use application::use_cases::feed::get_following;
|
|
use domain::models::feed::PageParams;
|
|
let page = PageParams {
|
|
page: q.page(),
|
|
per_page: q.per_page(),
|
|
};
|
|
let result = get_following(&*s.follows, &uid, page).await?;
|
|
Ok(Json(serde_json::json!({
|
|
"total": result.total,
|
|
"items": result.items.iter().map(to_user_response).collect::<Vec<_>>(),
|
|
})))
|
|
}
|
|
|
|
pub async fn get_users(
|
|
State(s): State<AppState>,
|
|
Query(params): Query<std::collections::HashMap<String, String>>,
|
|
) -> Result<Json<serde_json::Value>, ApiError> {
|
|
use domain::models::feed::PageParams;
|
|
let page = params
|
|
.get("page")
|
|
.and_then(|v| v.parse::<u64>().ok())
|
|
.unwrap_or(1);
|
|
let per_page = params
|
|
.get("per_page")
|
|
.and_then(|v| v.parse::<u64>().ok())
|
|
.unwrap_or(20);
|
|
let page_params = PageParams { page, per_page };
|
|
|
|
if let Some(q) = params.get("q").filter(|q| !q.trim().is_empty()) {
|
|
let result = search_users(&*s.search, q, page_params).await?;
|
|
let users: Vec<_> = result
|
|
.items
|
|
.iter()
|
|
.map(crate::handlers::auth::to_user_response)
|
|
.collect();
|
|
return Ok(Json(serde_json::json!({
|
|
"items": users, "total": result.total, "page": result.page, "per_page": result.per_page
|
|
})));
|
|
}
|
|
|
|
let all = list_users(&*s.users).await?;
|
|
let total = all.len() as i64;
|
|
let start = ((page - 1) * per_page) as usize;
|
|
let items: Vec<_> = all
|
|
.into_iter()
|
|
.skip(start)
|
|
.take(per_page as usize)
|
|
.map(|u| {
|
|
serde_json::json!({
|
|
"id": u.id.as_uuid(),
|
|
"username": u.username,
|
|
"displayName": u.display_name,
|
|
"avatarUrl": u.avatar_url,
|
|
"bio": u.bio,
|
|
"headerUrl": null,
|
|
"customCss": null,
|
|
"local": true,
|
|
"isFollowedByViewer": false,
|
|
"joinedAt": null,
|
|
})
|
|
})
|
|
.collect();
|
|
Ok(Json(serde_json::json!({
|
|
"items": items, "total": total, "page": page, "per_page": per_page
|
|
})))
|
|
}
|
|
|
|
pub async fn get_user_count(
|
|
State(s): State<AppState>,
|
|
) -> Result<Json<serde_json::Value>, ApiError> {
|
|
let count = s.users.count().await?;
|
|
Ok(Json(serde_json::json!({ "count": count })))
|
|
}
|