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}