api/routes/users/
post.rs

1//! # User Creation Routes
2//!
3//! - `POST /api/users`: Create a single non-admin user
4//! - `POST /api/users/bulk`: Create multiple non-admin users
5//!
6//! All routes require admin privileges.
7
8use axum::{
9    extract::State,
10    http::StatusCode,
11    response::IntoResponse,
12    Json,
13};
14use util::state::AppState;
15use crate::response::ApiResponse;
16use db::models::user::Model as UserModel;
17use crate::routes::users::common::{CreateUserRequest, BulkCreateUsersRequest, UserResponse};
18use validator::Validate;
19
20/// POST /api/users
21///
22/// Creates a single **non-admin** user. Admin-only access.
23///
24/// ### Request Body
25/// ```json
26/// {
27///   "username": "u12345678",
28///   "email": "[email protected]",
29///   "password": "securepassword"
30/// }
31/// ```
32///
33/// ### Response: 201 Created
34/// - JSON body with full user object (excluding password)
35///
36/// ### Errors:
37/// - 400 Bad Request — Validation failure
38/// - 409 Conflict — Duplicate username/email
39pub async fn create_user(
40    State(app_state): State<AppState>,
41    Json(req): Json<CreateUserRequest>,
42) -> impl IntoResponse {
43    let db = app_state.db();
44
45    if let Err(e) = req.validate() {
46        return (
47            StatusCode::BAD_REQUEST,
48            Json(ApiResponse::<()>::error(format!("Validation failed: {e}"))),
49        )
50            .into_response();
51    }
52
53    match UserModel::create(db, &req.username, &req.email, &req.password, false).await {
54        Ok(user) => (
55            StatusCode::CREATED,
56            Json(ApiResponse::<UserResponse>::success(
57                user.into(),
58                "User created successfully",
59            )),
60        )
61            .into_response(),
62        Err(e) => {
63            let msg = if e.to_string().contains("UNIQUE constraint failed") {
64                "A user with this username or email already exists".to_string()
65            } else {
66                format!("Database error: {e}")
67            };
68            (
69                StatusCode::CONFLICT,
70                Json(ApiResponse::<()>::error(msg)),
71            )
72                .into_response()
73        }
74    }
75}
76
77
78/// POST /api/users/bulk
79///
80/// Creates multiple **non-admin** users. Admin-only access.
81///
82/// ### Request Body
83/// ```json
84/// {
85///   "users": [
86///     { "username": "u001", "email": "[email protected]", "password": "pw1" },
87///     { "username": "u002", "email": "[email protected]", "password": "pw2" }
88///   ]
89/// }
90/// ```
91///
92/// ### Response: 201 Created
93/// - JSON array of created user objects
94///
95/// ### Errors:
96/// - 400 Bad Request — If validation fails
97/// - 409 Conflict — If one user fails to insert (first error returned)
98pub async fn bulk_create_users(
99    State(app_state): State<AppState>,
100    Json(req): Json<BulkCreateUsersRequest>,
101) -> impl IntoResponse {
102    let db = app_state.db();
103
104    if let Err(e) = req.validate() {
105        return (
106            StatusCode::BAD_REQUEST,
107            Json(ApiResponse::<()>::error(format!("Validation failed: {e}"))),
108        )
109        .into_response();
110    }
111
112    let mut results = Vec::new();
113
114    for user_req in req.users {
115        match UserModel::create(db, &user_req.username, &user_req.email, &user_req.password, false).await {
116            Ok(u) => results.push(UserResponse::from(u)),
117            Err(e) => {
118                let msg = if e.to_string().contains("UNIQUE constraint failed") {
119                    format!("A user with this username or email already exists: {}", user_req.username)
120                } else {
121                    format!("Database error while creating {}: {}", user_req.username, e)
122                };
123
124                return (
125                    StatusCode::CONFLICT,
126                    Json(ApiResponse::<()>::error(msg)),
127                )
128                .into_response();
129            }
130        }
131    }
132
133    (
134        StatusCode::CREATED,
135        Json(ApiResponse::<Vec<UserResponse>>::success(
136            results,
137            "Users created successfully",
138        )),
139    )
140    .into_response()
141}