api/routes/test/
post.rs

1//! POST handlers for `/api/test`.
2
3use axum::{extract::State, http::StatusCode, response::IntoResponse, Json};
4use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set};
5use util::state::AppState;
6use validator::Validate;
7
8use crate::response::ApiResponse;
9use db::models::user::{
10    ActiveModel as UserActiveModel, Column as UserColumn, Entity as UserEntity, Model as UserModel,
11};
12
13use super::common::{TestUserResponse, UpsertUserRequest};
14
15/// POST `/api/test/users`
16///
17/// Create-or-update (**idempotent**) a user.  
18/// - If the `username` exists, updates `email`, `password`, and `admin`.
19/// - If it does not exist, creates a new user.
20/// - This endpoint is for **test environments only**.
21///
22/// # Request body
23/// ```json
24/// {
25///   "username": "student42",
26///   "email": "[email protected]",
27///   "password": "secret",
28///   "admin": false
29/// }
30/// ```
31///
32/// ## Field notes
33/// - `admin` is optional and defaults to `false`.
34/// - `password` is stored as a hashed value.
35///
36/// # Response examples
37///
38/// ## 201 Created (new user)
39/// ```json
40/// {
41///   "success": true,
42///   "data": {
43///     "id": 1,
44///     "username": "student42",
45///     "email": "[email protected]",
46///     "admin": false
47///   },
48///   "message": "User created"
49/// }
50/// ```
51///
52/// ## 200 OK (updated existing user)
53/// ```json
54/// {
55///   "success": true,
56///   "data": {
57///     "id": 1,
58///     "username": "student42",
59///     "email": "[email protected]",
60///     "admin": true
61///   },
62///   "message": "User updated"
63/// }
64/// ```
65///
66/// ## 400 Bad Request (validation error)
67/// ```json
68/// {
69///   "success": false,
70///   "data": null,
71///   "message": "Validation failed: email: must be a valid email"
72/// }
73/// ```
74///
75/// ## 409 Conflict (username/email already exists)
76/// ```json
77/// {
78///   "success": false,
79///   "data": null,
80///   "message": "A user with this username or email already exists"
81/// }
82/// ```
83///
84/// ## 500 Internal Server Error
85/// ```json
86/// {
87///   "success": false,
88///   "data": null,
89///   "message": "Database error: <details>"
90/// }
91/// ```
92pub async fn upsert_user(
93    State(app): State<AppState>,
94    Json(req): Json<UpsertUserRequest>,
95) -> impl IntoResponse {
96    if let Err(e) = req.validate() {
97        return (
98            StatusCode::BAD_REQUEST,
99            Json(ApiResponse::<()>::error(format!("Validation failed: {e}"))),
100        )
101            .into_response();
102    }
103
104    let db = app.db();
105    let admin = req.admin.unwrap_or(false);
106
107    match UserEntity::find()
108        .filter(UserColumn::Username.eq(req.username.clone()))
109        .one(db)
110        .await
111    {
112        Ok(Some(existing)) => {
113            // Update existing user
114            let hash = UserModel::hash_password(&req.password);
115            let mut am: UserActiveModel = existing.into();
116            am.email = Set(req.email.clone());
117            am.admin = Set(admin);
118            am.password_hash = Set(hash);
119
120            match am.update(db).await {
121                Ok(updated) => (
122                    StatusCode::OK,
123                    Json(ApiResponse::success(
124                        TestUserResponse::from(updated),
125                        String::from("User updated"),
126                    )),
127                )
128                    .into_response(),
129                Err(e) => (
130                    StatusCode::INTERNAL_SERVER_ERROR,
131                    Json(ApiResponse::<()>::error(format!("Database error: {e}"))),
132                )
133                    .into_response(),
134            }
135        }
136        Ok(None) => {
137            // Create new user
138            match UserModel::create(db, &req.username, &req.email, &req.password, admin).await {
139                Ok(user) => (
140                    StatusCode::CREATED,
141                    Json(ApiResponse::success(
142                        TestUserResponse::from(user),
143                        String::from("User created"),
144                    )),
145                )
146                    .into_response(),
147                Err(e) => {
148                    let msg = if e.to_string().contains("UNIQUE constraint failed")
149                        || e.to_string().to_lowercase().contains("unique")
150                    {
151                        String::from("A user with this username or email already exists")
152                    } else {
153                        format!("Database error: {e}")
154                    };
155                    (
156                        StatusCode::CONFLICT,
157                        Json(ApiResponse::<()>::error(msg)),
158                    )
159                        .into_response()
160                }
161            }
162        }
163        Err(e) => (
164            StatusCode::INTERNAL_SERVER_ERROR,
165            Json(ApiResponse::<()>::error(format!("Database error: {e}"))),
166        )
167            .into_response(),
168    }
169}