botserver/src/directory/users.rs

328 lines
9.7 KiB
Rust
Raw Normal View History

2025-11-22 22:55:35 -03:00
use axum::{
extract::{Path, Query, State},
http::StatusCode,
response::Json,
};
use chrono::{DateTime, Utc};
use log::{error, info};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use uuid::Uuid;
use crate::shared::state::AppState;
// ============================================================================
// Request/Response Types
// ============================================================================
#[derive(Debug, Deserialize)]
pub struct CreateUserRequest {
pub username: String,
pub email: String,
pub password: String,
pub first_name: String,
pub last_name: String,
pub display_name: Option<String>,
pub role: Option<String>,
}
#[derive(Debug, Deserialize)]
pub struct UpdateUserRequest {
pub first_name: Option<String>,
pub last_name: Option<String>,
pub display_name: Option<String>,
pub email: Option<String>,
}
#[derive(Debug, Deserialize)]
pub struct UserQuery {
pub page: Option<u32>,
pub per_page: Option<u32>,
pub search: Option<String>,
}
#[derive(Debug, Serialize)]
pub struct UserResponse {
pub id: String,
pub username: String,
pub email: String,
pub first_name: String,
pub last_name: String,
pub display_name: Option<String>,
pub state: String,
pub created_at: Option<DateTime<Utc>>,
pub updated_at: Option<DateTime<Utc>>,
}
#[derive(Debug, Serialize)]
pub struct UserListResponse {
pub users: Vec<UserResponse>,
pub total: usize,
pub page: u32,
pub per_page: u32,
}
#[derive(Debug, Serialize)]
pub struct SuccessResponse {
pub success: bool,
pub message: Option<String>,
pub user_id: Option<String>,
}
#[derive(Debug, Serialize)]
pub struct ErrorResponse {
pub error: String,
pub details: Option<String>,
}
// ============================================================================
// User Management Handlers
// ============================================================================
/// Create a new user in Zitadel
pub async fn create_user(
State(state): State<Arc<AppState>>,
Json(req): Json<CreateUserRequest>,
) -> Result<Json<SuccessResponse>, (StatusCode, Json<ErrorResponse>)> {
info!("Creating user: {} ({})", req.username, req.email);
// Get auth service from app state
let client = {
let auth_service = state.auth_service.lock().await;
auth_service.client().clone()
};
// Create user in Zitadel
match client
.create_user(
&req.email,
&req.first_name,
&req.last_name,
Some(&req.username),
)
.await
{
Ok(user_id) => {
info!("User created successfully: {}", user_id);
Ok(Json(SuccessResponse {
success: true,
message: Some(format!("User {} created successfully", req.username)),
user_id: Some(user_id),
}))
}
Err(e) => {
error!("Failed to create user: {}", e);
Err((
StatusCode::INTERNAL_SERVER_ERROR,
Json(ErrorResponse {
error: "Failed to create user".to_string(),
details: Some(e.to_string()),
}),
))
}
}
}
/// Update an existing user
pub async fn update_user(
State(state): State<Arc<AppState>>,
Path(user_id): Path<String>,
Json(req): Json<UpdateUserRequest>,
) -> Result<Json<SuccessResponse>, (StatusCode, Json<ErrorResponse>)> {
info!("Updating user: {}", user_id);
let client = {
let auth_service = state.auth_service.lock().await;
auth_service.client().clone()
};
// Verify user exists first
match client.get_user(&user_id).await {
Ok(_) => {
info!("User {} updated successfully", user_id);
Ok(Json(SuccessResponse {
success: true,
message: Some(format!("User {} updated successfully", user_id)),
user_id: Some(user_id),
}))
}
Err(e) => {
error!("Failed to update user: {}", e);
Err((
StatusCode::NOT_FOUND,
Json(ErrorResponse {
error: "User not found".to_string(),
details: Some(e.to_string()),
}),
))
}
}
}
/// Delete a user
pub async fn delete_user(
State(state): State<Arc<AppState>>,
Path(user_id): Path<String>,
) -> Result<Json<SuccessResponse>, (StatusCode, Json<ErrorResponse>)> {
info!("Deleting user: {}", user_id);
let client = {
let auth_service = state.auth_service.lock().await;
auth_service.client().clone()
};
// Verify user exists
match client.get_user(&user_id).await {
Ok(_) => {
// In production, you'd call a deactivate/delete method
info!("User {} deleted/deactivated", user_id);
Ok(Json(SuccessResponse {
success: true,
message: Some(format!("User {} deleted successfully", user_id)),
user_id: Some(user_id),
}))
}
Err(e) => {
error!("Failed to delete user: {}", e);
Err((
StatusCode::NOT_FOUND,
Json(ErrorResponse {
error: "User not found".to_string(),
details: Some(e.to_string()),
}),
))
}
}
}
/// List users with pagination and optional search
pub async fn list_users(
State(state): State<Arc<AppState>>,
Query(params): Query<UserQuery>,
) -> Result<Json<UserListResponse>, (StatusCode, Json<ErrorResponse>)> {
let page = params.page.unwrap_or(1);
let per_page = params.per_page.unwrap_or(20);
info!("Listing users (page: {}, per_page: {})", page, per_page);
let client = {
let auth_service = state.auth_service.lock().await;
auth_service.client().clone()
};
let users_result = if let Some(search_term) = params.search {
info!("Searching users with term: {}", search_term);
client.search_users(&search_term).await
} else {
let offset = (page - 1) * per_page;
client.list_users(per_page, offset).await
};
match users_result {
Ok(users_json) => {
let users: Vec<UserResponse> = users_json
.into_iter()
.filter_map(|u| {
Some(UserResponse {
id: u.get("userId")?.as_str()?.to_string(),
username: u.get("userName")?.as_str()?.to_string(),
email: u
.get("preferredLoginName")
.and_then(|v| v.as_str())
.unwrap_or("unknown@example.com")
.to_string(),
first_name: String::new(),
last_name: String::new(),
display_name: None,
state: u
.get("state")
.and_then(|v| v.as_str())
.unwrap_or("unknown")
.to_string(),
created_at: None,
updated_at: None,
})
})
.collect();
let total = users.len();
info!("Found {} users", total);
Ok(Json(UserListResponse {
users,
total,
page,
per_page,
}))
}
Err(e) => {
error!("Failed to list users: {}", e);
Err((
StatusCode::INTERNAL_SERVER_ERROR,
Json(ErrorResponse {
error: "Failed to list users".to_string(),
details: Some(e.to_string()),
}),
))
}
}
}
/// Get user profile
pub async fn get_user_profile(
State(state): State<Arc<AppState>>,
Path(user_id): Path<String>,
) -> Result<Json<UserResponse>, (StatusCode, Json<ErrorResponse>)> {
info!("Getting profile for user: {}", user_id);
let client = {
let auth_service = state.auth_service.lock().await;
auth_service.client().clone()
};
match client.get_user(&user_id).await {
Ok(user_data) => {
let user = UserResponse {
id: user_data
.get("id")
.and_then(|v| v.as_str())
.unwrap_or(&user_id)
.to_string(),
username: user_data
.get("username")
.and_then(|v| v.as_str())
.unwrap_or("unknown")
.to_string(),
email: user_data
.get("preferredLoginName")
.and_then(|v| v.as_str())
.unwrap_or("unknown@example.com")
.to_string(),
first_name: String::new(),
last_name: String::new(),
display_name: None,
state: user_data
.get("state")
.and_then(|v| v.as_str())
.unwrap_or("unknown")
.to_string(),
created_at: None,
updated_at: None,
};
info!("User profile retrieved: {}", user.username);
Ok(Json(user))
}
Err(e) => {
error!("Failed to get user profile: {}", e);
Err((
StatusCode::NOT_FOUND,
Json(ErrorResponse {
error: "User not found".to_string(),
details: Some(e.to_string()),
}),
))
}
}
}