Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions packages/api-server/src/domains/users/dto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,13 @@ pub struct UpdateUserDto {
pub bio: Option<String>,
}

/// 팔로우 상태 응답
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct FollowStatusResponse {
/// 팔로우 여부
pub is_following: bool,
}

/// 사용자 통계 응답
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct UserStatsResponse {
Expand Down
77 changes: 74 additions & 3 deletions packages/api-server/src/domains/users/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@

use axum::{
extract::{Path, Query, State},
http::StatusCode,
middleware::from_fn_with_state,
routing::get,
routing::{get, post},
Extension, Json, Router,
};
use uuid::Uuid;
Expand All @@ -19,8 +20,8 @@ use crate::{

use super::{
dto::{
SavedItem, TryItem, UpdateUserDto, UserActivitiesQuery, UserActivityItem, UserActivityType,
UserResponse, UserStatsResponse,
FollowStatusResponse, SavedItem, TryItem, UpdateUserDto, UserActivitiesQuery,
UserActivityItem, UserActivityType, UserResponse, UserStatsResponse,
},
service,
};
Expand Down Expand Up @@ -199,6 +200,71 @@ pub async fn get_my_saved(
Ok(Json(result))
}

/// POST /api/v1/users/{user_id}/follow - 팔로우
#[utoipa::path(
post,
path = "/api/v1/users/{user_id}/follow",
tag = "users",
security(("bearer_auth" = [])),
params(("user_id" = Uuid, Path, description = "팔로우할 사용자 ID")),
responses(
(status = 204, description = "팔로우 성공"),
(status = 400, description = "자기 자신을 팔로우할 수 없음"),
(status = 401, description = "인증 필요"),
(status = 404, description = "사용자를 찾을 수 없음")
)
)]
pub async fn follow_user_handler(
State(state): State<AppState>,
Extension(user): Extension<User>,
Path(target_id): Path<Uuid>,
) -> AppResult<StatusCode> {
service::follow_user(&state.db, user.id, target_id).await?;
Ok(StatusCode::NO_CONTENT)
}

/// DELETE /api/v1/users/{user_id}/follow - 언팔로우
#[utoipa::path(
delete,
path = "/api/v1/users/{user_id}/follow",
tag = "users",
security(("bearer_auth" = [])),
params(("user_id" = Uuid, Path, description = "언팔로우할 사용자 ID")),
responses(
(status = 204, description = "언팔로우 성공"),
(status = 401, description = "인증 필요")
)
)]
pub async fn unfollow_user_handler(
State(state): State<AppState>,
Extension(user): Extension<User>,
Path(target_id): Path<Uuid>,
) -> AppResult<StatusCode> {
service::unfollow_user(&state.db, user.id, target_id).await?;
Ok(StatusCode::NO_CONTENT)
}

/// GET /api/v1/users/{user_id}/follow-status - 팔로우 여부 확인
#[utoipa::path(
get,
path = "/api/v1/users/{user_id}/follow-status",
tag = "users",
security(("bearer_auth" = [])),
params(("user_id" = Uuid, Path, description = "대상 사용자 ID")),
responses(
(status = 200, description = "팔로우 상태 조회 성공", body = FollowStatusResponse),
(status = 401, description = "인증 필요")
)
)]
pub async fn get_follow_status(
State(state): State<AppState>,
Extension(user): Extension<User>,
Path(target_id): Path<Uuid>,
) -> AppResult<Json<FollowStatusResponse>> {
let is_following = service::check_is_following(&state.db, user.id, target_id).await?;
Ok(Json(FollowStatusResponse { is_following }))
}

/// Users 도메인 라우터
pub fn router(app_config: AppConfig) -> Router<AppState> {
let protected_routes = Router::new()
Expand All @@ -207,6 +273,11 @@ pub fn router(app_config: AppConfig) -> Router<AppState> {
.route("/me/stats", get(get_my_stats))
.route("/me/tries", get(get_my_tries))
.route("/me/saved", get(get_my_saved))
.route(
"/{user_id}/follow",
post(follow_user_handler).delete(unfollow_user_handler),
)
.route("/{user_id}/follow-status", get(get_follow_status))
.route_layer(from_fn_with_state(app_config, auth_middleware));

Router::new()
Expand Down
Loading
Loading