feat: add include query param and sessions to unified /me endpoint
GET /v1/iam/users/me now supports ?include=hackathon,qr,mentor,sessions to control which module profiles are returned. Omitting the param returns all modules. Also added dimentorin sessions data. Examples: /v1/iam/users/me → all modules /v1/iam/users/me?include=hackathon → only hackathon profile /v1/iam/users/me?include=mentor,sessions → mentor + sessions only Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
a4bbc73c7e
commit
5667a0d608
@@ -202,6 +202,7 @@ use utoipa::OpenApi;
|
|||||||
imphnen_iam::users::infrastructure::http::dto::HackathonProfileDto,
|
imphnen_iam::users::infrastructure::http::dto::HackathonProfileDto,
|
||||||
imphnen_iam::users::infrastructure::http::dto::QrProfileDto,
|
imphnen_iam::users::infrastructure::http::dto::QrProfileDto,
|
||||||
imphnen_iam::users::infrastructure::http::dto::MentorProfileDto,
|
imphnen_iam::users::infrastructure::http::dto::MentorProfileDto,
|
||||||
|
imphnen_iam::users::infrastructure::http::dto::SessionProfileDto,
|
||||||
GachaClaimDetailDto, GachaClaimCreateRequestDto,
|
GachaClaimDetailDto, GachaClaimCreateRequestDto,
|
||||||
GachaCreditDto, GachaCreditAddRequestDto,
|
GachaCreditDto, GachaCreditAddRequestDto,
|
||||||
GachaItemDto, GachaItemCreateRequestDto,
|
GachaItemDto, GachaItemCreateRequestDto,
|
||||||
|
|||||||
@@ -141,6 +141,21 @@ pub struct UsersMeResponseDto {
|
|||||||
pub qr: Option<QrProfileDto>,
|
pub qr: Option<QrProfileDto>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub mentor: Option<MentorProfileDto>,
|
pub mentor: Option<MentorProfileDto>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub sessions: Option<Vec<SessionProfileDto>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||||
|
pub struct SessionProfileDto {
|
||||||
|
pub id: String,
|
||||||
|
pub topic: String,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub scheduled_at: String,
|
||||||
|
pub duration_minutes: i32,
|
||||||
|
pub session_type: String,
|
||||||
|
pub status: String,
|
||||||
|
pub role: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
use super::super::dto::{
|
use super::super::dto::{
|
||||||
HackathonProfileDto, MentorProfileDto, QrProfileDto, UsersDetailItemDto,
|
HackathonProfileDto, MentorProfileDto, QrProfileDto, SessionProfileDto,
|
||||||
UsersListItemDto, UsersMeResponseDto,
|
UsersDetailItemDto, UsersListItemDto, UsersMeResponseDto,
|
||||||
};
|
};
|
||||||
use crate::require_permissions;
|
use crate::require_permissions;
|
||||||
use crate::users::domain::UserService;
|
use crate::users::domain::UserService;
|
||||||
use axum::{Extension, extract::Path, http::HeaderMap, response::IntoResponse};
|
use axum::{
|
||||||
|
Extension,
|
||||||
|
extract::{Path, Query},
|
||||||
|
http::HeaderMap,
|
||||||
|
response::IntoResponse,
|
||||||
|
};
|
||||||
use imphnen_entities::{
|
use imphnen_entities::{
|
||||||
PermissionsEnum, ResponseListSuccessDto, ResponseSuccessDto,
|
PermissionsEnum, ResponseListSuccessDto, ResponseSuccessDto,
|
||||||
};
|
};
|
||||||
@@ -13,9 +18,17 @@ use imphnen_utils::{ApiPaginated, ApiSuccess, AppError};
|
|||||||
use paginator_axum::PaginationQuery;
|
use paginator_axum::PaginationQuery;
|
||||||
use paginator_utils::PaginatorResponse;
|
use paginator_utils::PaginatorResponse;
|
||||||
use sea_orm::DatabaseConnection;
|
use sea_orm::DatabaseConnection;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::collections::HashSet;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct MeQueryParams {
|
||||||
|
/// Comma-separated list of modules to include: hackathon,qr,mentor,sessions
|
||||||
|
pub include: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[utoipa::path(
|
#[utoipa::path(
|
||||||
get,
|
get,
|
||||||
path = "/v1/iam/users",
|
path = "/v1/iam/users",
|
||||||
@@ -86,6 +99,9 @@ pub async fn get_user_by_id(
|
|||||||
get,
|
get,
|
||||||
path = "/v1/iam/users/me",
|
path = "/v1/iam/users/me",
|
||||||
security(("Bearer" = [])),
|
security(("Bearer" = [])),
|
||||||
|
params(
|
||||||
|
("include" = Option<String>, Query, description = "Comma-separated modules to include: hackathon,qr,mentor,sessions. Omit to include all."),
|
||||||
|
),
|
||||||
responses(
|
responses(
|
||||||
(status = 200, description = "[USER] Get current user profile (unified across all modules)", body = ResponseSuccessDto<UsersMeResponseDto>)
|
(status = 200, description = "[USER] Get current user profile (unified across all modules)", body = ResponseSuccessDto<UsersMeResponseDto>)
|
||||||
),
|
),
|
||||||
@@ -95,6 +111,7 @@ pub async fn get_user_me(
|
|||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
Extension(state): Extension<AppState>,
|
Extension(state): Extension<AppState>,
|
||||||
Extension(service): Extension<Arc<dyn UserService>>,
|
Extension(service): Extension<Arc<dyn UserService>>,
|
||||||
|
Query(params): Query<MeQueryParams>,
|
||||||
) -> Result<impl IntoResponse, AppError> {
|
) -> Result<impl IntoResponse, AppError> {
|
||||||
let (claims, _) = crate::permissions_guard(
|
let (claims, _) = crate::permissions_guard(
|
||||||
headers,
|
headers,
|
||||||
@@ -111,15 +128,43 @@ pub async fn get_user_me(
|
|||||||
let user_uuid = Uuid::parse_str(&claims.user_id).ok();
|
let user_uuid = Uuid::parse_str(&claims.user_id).ok();
|
||||||
let db = &state.postgres_connection.conn;
|
let db = &state.postgres_connection.conn;
|
||||||
|
|
||||||
let hackathon = fetch_hackathon_profile(db, user_uuid).await;
|
let includes: Option<HashSet<String>> = params.include.map(|s| {
|
||||||
let qr = fetch_qr_profile(db, user_uuid).await;
|
s.split(',').map(|v| v.trim().to_lowercase()).collect()
|
||||||
let mentor = fetch_mentor_profile(db, user_uuid).await;
|
});
|
||||||
|
let should_include = |module: &str| -> bool {
|
||||||
|
match &includes {
|
||||||
|
None => true,
|
||||||
|
Some(set) => set.contains(module),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let hackathon = if should_include("hackathon") {
|
||||||
|
fetch_hackathon_profile(db, user_uuid).await
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let qr = if should_include("qr") {
|
||||||
|
fetch_qr_profile(db, user_uuid).await
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let mentor = if should_include("mentor") {
|
||||||
|
fetch_mentor_profile(db, user_uuid).await
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let sessions = if should_include("sessions") {
|
||||||
|
fetch_sessions(db, user_uuid).await
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
Ok(ApiSuccess(UsersMeResponseDto {
|
Ok(ApiSuccess(UsersMeResponseDto {
|
||||||
user: user_dto,
|
user: user_dto,
|
||||||
hackathon,
|
hackathon,
|
||||||
qr,
|
qr,
|
||||||
mentor,
|
mentor,
|
||||||
|
sessions,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,6 +229,56 @@ async fn fetch_mentor_profile(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn fetch_sessions(
|
||||||
|
db: &DatabaseConnection,
|
||||||
|
user_id: Option<Uuid>,
|
||||||
|
) -> Option<Vec<SessionProfileDto>> {
|
||||||
|
let uid = user_id?;
|
||||||
|
let pool = db.get_postgres_connection_pool();
|
||||||
|
let rows = sqlx::query_as::<_, SessionRow>(
|
||||||
|
r#"SELECT id, topic, description, scheduled_at, duration_minutes,
|
||||||
|
session_type, status,
|
||||||
|
CASE WHEN mentor_id = $1 THEN 'mentor' ELSE 'mentee' END as role
|
||||||
|
FROM sessions
|
||||||
|
WHERE mentor_id = $1 OR mentee_id = $1
|
||||||
|
ORDER BY scheduled_at DESC
|
||||||
|
LIMIT 20"#,
|
||||||
|
)
|
||||||
|
.bind(uid)
|
||||||
|
.fetch_all(pool)
|
||||||
|
.await
|
||||||
|
.ok()?;
|
||||||
|
if rows.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(
|
||||||
|
rows.into_iter()
|
||||||
|
.map(|s| SessionProfileDto {
|
||||||
|
id: s.id.to_string(),
|
||||||
|
topic: s.topic,
|
||||||
|
description: s.description,
|
||||||
|
scheduled_at: s.scheduled_at.to_rfc3339(),
|
||||||
|
duration_minutes: s.duration_minutes,
|
||||||
|
session_type: s.session_type,
|
||||||
|
status: s.status,
|
||||||
|
role: s.role,
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(sqlx::FromRow)]
|
||||||
|
struct SessionRow {
|
||||||
|
id: Uuid,
|
||||||
|
topic: String,
|
||||||
|
description: Option<String>,
|
||||||
|
scheduled_at: chrono::DateTime<chrono::Utc>,
|
||||||
|
duration_minutes: i32,
|
||||||
|
session_type: String,
|
||||||
|
status: String,
|
||||||
|
role: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(sqlx::FromRow)]
|
#[derive(sqlx::FromRow)]
|
||||||
struct HackathonRow {
|
struct HackathonRow {
|
||||||
is_admin: bool,
|
is_admin: bool,
|
||||||
|
|||||||
Reference in New Issue
Block a user