use crate::{AppState, MetaRequestDto}; use crate::{ MessageResponseDto, ResponseListSuccessDto, ResponseSuccessDto, TeamsCreateRequestDto, TeamsDetailItemDto, TeamsListItemDto, permissions_guard, TeamsUpdateRequestDto, TeamInviteRequestDto, TeamAcceptInvitationRequestDto, TeamMemberDto, TeamsSearchQueryDto, PublicTeamsListItemDto, PublicTeamsDetailItemDto, AdminTeamsListItemDto, AdminTeamsDetailItemDto, PermissionsEnum }; use axum::response::Response; use axum::extract::Path; use axum::http::HeaderMap; use axum::response::IntoResponse; use axum::{Extension, Json}; use super::teams_service::{TeamsServiceTrait, TeamsService}; use axum::Router; // Helper function for endpoints requiring authentication without specific permissions async fn authenticated( headers: HeaderMap, state: Extension, f: F, ) -> Response where F: FnOnce(crate::Claims, AppState) -> Fut, Fut: std::future::Future + Send, { match permissions_guard(headers, state, vec![]).await { Ok((claims, state)) => f(claims, state).await, Err(response) => response, } } // Helper function for endpoints requiring specific permissions async fn with_perms( headers: HeaderMap, state: Extension, perms: Vec, f: F, ) -> Response where F: FnOnce(crate::Claims, AppState) -> Fut, Fut: std::future::Future + Send, { match permissions_guard(headers, state, perms).await { Ok((claims, state)) => f(claims, state).await, Err(response) => response, } } #[utoipa::path( get, security( ("Bearer" = []) ), path = "/v1/teams", params( ("page" = Option, Query, description = "Page number"), ("per_page" = Option, Query, description = "Items per page"), ("search" = Option, Query, description = "Search keyword"), ("sort_by" = Option, Query, description = "Sort by field"), ("order" = Option, Query, description = "Order ASC or DESC"), ("filter" = Option, Query, description = "Filter value"), ("filter_by" = Option, Query, description = "Field to filter by"), ), responses( (status = 200, description = "Get team list", body = ResponseListSuccessDto>), (status = 200, description = "Get public team list", body = ResponseListSuccessDto>) ), tag = "Teams" )] pub async fn get_team_list( Extension(state): Extension, axum::extract::Query(meta): axum::extract::Query, ) -> impl IntoResponse { TeamsService::get_public_team_list(&state, meta).await } #[utoipa::path( get, path = "/v1/teams/{id}", params( ("id" = String, Path, description = "Team ID") ), responses( (status = 200, description = "Get team by ID", body = ResponseSuccessDto), (status = 200, description = "Get public team by ID", body = ResponseSuccessDto) ), tag = "Teams" )] pub async fn get_team_by_id( Extension(state): Extension, Path(id): Path, ) -> impl IntoResponse { TeamsService::get_public_team_by_id(&state, id).await } #[utoipa::path( post, security( ("Bearer" = []) ), path = "/v1/teams/create", request_body = TeamsCreateRequestDto, responses( (status = 200, description = "Create new team", body = ResponseSuccessDto) ), tag = "Teams" )] pub async fn post_create_team( headers: HeaderMap, Extension(state): Extension, Json(payload): Json, ) -> impl IntoResponse { authenticated(headers, Extension(state), move |claims, state| TeamsService::create_team(&state, claims, payload)).await } #[utoipa::path( put, security( ("Bearer" = []) ), path = "/v1/teams/update/{id}", params( ("id" = String, Path, description = "Team ID") ), request_body = TeamsUpdateRequestDto, responses( (status = 200, description = "Update team", body = MessageResponseDto) ), tag = "Teams" )] pub async fn put_update_team( headers: HeaderMap, Extension(state): Extension, Path(id): Path, Json(payload): Json, ) -> impl IntoResponse { authenticated(headers, Extension(state), move |claims, state| TeamsService::update_team(&state, claims, id, payload)).await } #[utoipa::path( delete, security( ("Bearer" = []) ), path = "/v1/teams/delete/{id}", params( ("id" = String, Path, description = "Team ID") ), responses( (status = 200, description = "Delete team", body = MessageResponseDto) ), tag = "Teams" )] pub async fn delete_team( headers: HeaderMap, Extension(state): Extension, Path(id): Path, ) -> impl IntoResponse { authenticated(headers, Extension(state), move |claims, state| TeamsService::delete_team(&state, claims, id)).await } #[utoipa::path( post, security( ("Bearer" = []) ), path = "/v1/teams/{id}/invite", params( ("id" = String, Path, description = "Team ID") ), request_body = TeamInviteRequestDto, responses( (status = 200, description = "Invite team members", body = ResponseSuccessDto) ), tag = "Teams" )] pub async fn post_invite_team_members( headers: HeaderMap, Extension(state): Extension, Path(team_id): Path, Json(payload): Json, ) -> impl IntoResponse { authenticated(headers, Extension(state), move |claims, state| TeamsService::invite_team_members(&state, claims, team_id, payload)).await } #[utoipa::path( post, security( ("Bearer" = []) ), path = "/v1/teams/accept/{token}", params( ("token" = String, Path, description = "Invitation token") ), responses( (status = 200, description = "Accept team invitation", body = MessageResponseDto) ), tag = "Teams" )] pub async fn post_accept_invitation( headers: HeaderMap, Extension(state): Extension, Path(token): Path, ) -> impl IntoResponse { let accept_dto = TeamAcceptInvitationRequestDto { token }; authenticated(headers, Extension(state), move |claims, state| TeamsService::accept_invitation(&state, claims, accept_dto)).await } #[utoipa::path( get, path = "/v1/teams/search", params( ("query" = Option, Query, description = "Search query"), ("open" = Option, Query, description = "Filter by open teams"), ("skills" = Option>, Query, description = "Filter by required skills"), ("location" = Option, Query, description = "Filter by location"), ("page" = Option, Query, description = "Page number"), ("per_page" = Option, Query, description = "Items per page"), ), responses( (status = 200, description = "Search teams", body = ResponseListSuccessDto>) ), tag = "Teams" )] pub async fn get_public_team_search( Extension(state): Extension, axum::extract::Query(search_params): axum::extract::Query, ) -> impl IntoResponse { TeamsService::search_teams(&state, search_params).await } #[utoipa::path( get, security( ("Bearer" = []) ), path = "/v1/teams/{id}/members", params( ("id" = String, Path, description = "Team ID") ), responses( (status = 200, description = "Get team members", body = ResponseSuccessDto>) ), tag = "Teams" )] pub async fn get_team_members( headers: HeaderMap, Extension(state): Extension, Path(id): Path, ) -> impl IntoResponse { authenticated(headers, Extension(state), move |claims, state| TeamsService::get_team_members(&state, claims, id)).await } #[utoipa::path( post, security( ("Bearer" = []) ), path = "/v1/teams/{id}/leave", params( ("id" = String, Path, description = "Team ID") ), responses( (status = 200, description = "Leave specific team", body = MessageResponseDto) ), tag = "Teams" )] pub async fn post_leave_team( headers: HeaderMap, Extension(state): Extension, Path(id): Path, ) -> impl IntoResponse { authenticated(headers, Extension(state), move |claims, state| TeamsService::leave_team(&state, claims, id)).await } #[utoipa::path( post, security( ("Bearer" = []) ), path = "/v1/teams/leave-me", responses( (status = 200, description = "Leave current team", body = MessageResponseDto) ), tag = "Teams" )] pub async fn post_leave_current_team( headers: HeaderMap, Extension(state): Extension, ) -> impl IntoResponse { authenticated(headers, Extension(state), |claims, state| TeamsService::leave_current_team(&state, claims)).await } #[utoipa::path( get, security( ("Bearer" = []) ), path = "/v1/teams/admin", params( ("page" = Option, Query, description = "Page number"), ("per_page" = Option, Query, description = "Items per page"), ("search" = Option, Query, description = "Search keyword"), ("sort_by" = Option, Query, description = "Sort by field"), ("order" = Option, Query, description = "Order ASC or DESC"), ("filter" = Option, Query, description = "Filter value"), ("filter_by" = Option, Query, description = "Field to filter by"), ), responses( (status = 200, description = "Get admin team list", body = ResponseListSuccessDto>) ), tag = "Teams - Admin" )] pub async fn get_admin_team_list( headers: HeaderMap, Extension(state): Extension, axum::extract::Query(meta): axum::extract::Query, ) -> Response { let state = state; with_perms(headers, axum::Extension(state), vec![PermissionsEnum::ReadListTeams], move |_claims, state| { TeamsService::get_admin_team_list(&state, meta) }).await } #[utoipa::path( get, security( ("Bearer" = []) ), path = "/v1/teams/admin/{id}", params( ("id" = String, Path, description = "Team ID") ), responses( (status = 200, description = "Get admin team by ID", body = ResponseSuccessDto) ), tag = "Teams - Admin" )] pub async fn get_admin_team_by_id( headers: HeaderMap, Extension(state): Extension, Path(id): Path, ) -> Response { let state = state; with_perms(headers, axum::Extension(state), vec![PermissionsEnum::ReadDetailTeams], move |_claims, state| { TeamsService::get_admin_team_by_id(&state, id) }).await } #[utoipa::path( get, security( ("Bearer" = []) ), path = "/v1/teams/admin/{id}/members", params( ("id" = String, Path, description = "Team ID") ), responses( (status = 200, description = "Get admin team members", body = ResponseSuccessDto>) ), tag = "Teams - Admin" )] pub async fn get_admin_team_members( headers: HeaderMap, Extension(state): Extension, Path(id): Path, ) -> Response { let state = state; with_perms(headers, axum::Extension(state), vec![PermissionsEnum::ReadDetailTeams], move |_claims, state| { TeamsService::get_admin_team_members(&state, id) }).await } pub fn teams_router() -> Router { Router::new() .route("/", axum::routing::get(get_team_list)) .route("/{id}", axum::routing::get(get_team_by_id)) .route("/create", axum::routing::post(post_create_team)) .route("/update/{id}", axum::routing::put(put_update_team)) .route("/delete/{id}", axum::routing::delete(delete_team)) .route("/{id}/invite", axum::routing::post(post_invite_team_members)) .route("/accept/{token}", axum::routing::post(post_accept_invitation)) .route("/search", axum::routing::get(get_public_team_search)) .route("/{id}/members", axum::routing::get(get_team_members)) .route("/{id}/leave", axum::routing::post(post_leave_team)) .route("/leave-me", axum::routing::post(post_leave_current_team)) }