Constant ROUTES_MOD
Source pub const ROUTES_MOD: &str = r#"use crate::auth::{authorize_user, hash_password};
use crate::guards::AuthClaims;
use crate::models::{ErrorResponse, SuccessResponse, UserInfo};
use crate::models::{LoginCredentials, RegistrationCredentials, User, UserEntity};
use crate::repositories::UserRepository;
use rocket::http::Status;
use rocket::http::{Cookie, CookieJar, SameSite};
use rocket::serde::json::Json;
use rocket::{State, delete, get, post, put, routes};
use std::sync::Arc;
use uuid::Uuid;
/// Registers a new user.
#[post("/register", data = "<credentials>")]
pub async fn register(
repo: &State<Arc<UserRepository>>,
credentials: Json<RegistrationCredentials>,
) -> Result<Json<SuccessResponse>, Json<ErrorResponse>> {
if let Ok(Some(_)) = repo.get_user_by_email(&credentials.email).await {
return Err(Json(ErrorResponse {
status: Status::Conflict.code,
message: "A user with this email already exists".to_string(),
}));
}
let hashed_password = match hash_password(credentials.password.clone()) {
Ok(hash) => hash,
Err(_) => {
return Err(Json(ErrorResponse {
status: Status::InternalServerError.code,
message: "Something went wrong, please try again later".to_string(),
}));
}
};
let _ = match repo
.create_user(&credentials.username, &credentials.email, &hashed_password)
.await
{
Ok(user) => user,
Err(_) => {
return Err(Json(ErrorResponse {
status: Status::InternalServerError.code,
message: "Failed to register account".to_string(),
}));
}
};
Ok(Json(SuccessResponse {
status: Status::Ok.code,
message: "User registered successfully".to_string(),
}))
}
/// Authenticates a user and sets an authentication cookie.
#[post("/login", data = "<credentials>")]
pub async fn login(
repo: &State<Arc<UserRepository>>,
credentials: Json<LoginCredentials>,
cookies: &CookieJar<'_>,
) -> Result<Json<SuccessResponse>, Json<ErrorResponse>> {
let user_entity = match repo.get_user_by_email(&credentials.email).await {
Ok(Some(user_entity)) => user_entity,
Ok(None) => {
return Err(Json(ErrorResponse {
status: Status::Unauthorized.code,
message: "Invalid email or password".to_string(),
}));
}
Err(_) => {
return Err(Json(ErrorResponse {
status: Status::InternalServerError.code,
message: "Something went wrong, please try again later".to_string(),
}));
}
};
let user = User {
id: user_entity.id.to_string(),
username: user_entity.username.clone(),
email: user_entity.email.clone(),
password: user_entity.password.clone(),
created_at: user_entity.created_at.to_rfc3339(),
};
let token = match authorize_user(&user, &credentials).await {
Ok(token) => token,
Err(_) => {
return Err(Json(ErrorResponse {
status: Status::Unauthorized.code,
message: "Invalid email or password".to_string(),
}));
}
};
// Set the token cookie (HTTP-only, secure)
#[allow(deprecated)]
let cookie = Cookie::build(("auth_token", token.clone()))
.http_only(true)
.secure(false) // Set to true in production with HTTPS
.same_site(SameSite::Lax)
.path("/")
.finish();
cookies.add(cookie);
Ok(Json(SuccessResponse {
status: Status::Ok.code,
message: "Login successful".to_string(),
}))
}
/// Logs out the current user by removing the authentication cookie.
#[post("/logout")]
pub fn logout(cookies: &CookieJar<'_>) -> Json<SuccessResponse> {
cookies.remove(Cookie::build(("auth_token", "")).path("/").build());
Json(SuccessResponse {
status: 200,
message: "Logged out successfully".to_string(),
})
}
/// Retrieves a single user by ID (requires authentication).
#[get("/users/<id>")]
pub async fn get_user(
_auth: AuthClaims,
repo: &State<Arc<UserRepository>>,
id: &str,
) -> Result<Json<UserEntity>, Json<ErrorResponse>> {
let uuid = match Uuid::parse_str(id) {
Ok(uuid) => uuid,
Err(_) => {
return Err(Json(ErrorResponse {
status: Status::BadRequest.code,
message: "Invalid user ID format".to_string(),
}));
}
};
let user = match repo.get_user_by_id(uuid).await {
Ok(Some(user)) => user,
Ok(None) => {
return Err(Json(ErrorResponse {
status: Status::NotFound.code,
message: "User not found".to_string(),
}));
}
Err(_) => {
return Err(Json(ErrorResponse {
status: Status::InternalServerError.code,
message: "Something went wrong, please try again later".to_string(),
}));
}
};
Ok(Json(user))
}
/// Retrieves a single user by email (requires authentication).
#[get("/user/<email>")]
pub async fn get_user_by_email(
_auth: AuthClaims,
repo: &State<Arc<UserRepository>>,
email: &str,
) -> Result<Json<UserInfo>, Json<ErrorResponse>> {
let user = match repo.get_user_by_email(email).await {
Ok(Some(user)) => user,
Ok(None) => {
return Err(Json(ErrorResponse {
status: Status::NotFound.code,
message: "User not found".to_string(),
}));
}
Err(_) => {
return Err(Json(ErrorResponse {
status: Status::InternalServerError.code,
message: "Something went wrong, please try again later".to_string(),
}));
}
};
Ok(Json(UserInfo {
id: user.id.to_string(),
username: user.username,
email: user.email,
created_at: user.created_at.to_rfc3339(),
}))
}
/// Updates an existing user's information by ID (requires authentication).
#[put("/update/<id>", data = "<credentials>")]
pub async fn update_user(
_auth: AuthClaims,
repo: &State<Arc<UserRepository>>,
id: &str,
credentials: Json<RegistrationCredentials>,
) -> Result<Json<UserEntity>, Json<ErrorResponse>> {
let uuid = match Uuid::parse_str(id) {
Ok(uuid) => uuid,
Err(_) => {
return Err(Json(ErrorResponse {
status: Status::BadRequest.code,
message: "Invalid user ID format".to_string(),
}));
}
};
// Check if the email is already in use by another user
if let Ok(Some(existing_user)) = repo.get_user_by_email(&credentials.email).await {
// If the email exists and it's not the user being updated
if existing_user.id != uuid {
return Err(Json(ErrorResponse {
status: Status::Conflict.code,
message: "A user with this email already exists".to_string(),
}));
}
}
let hashed_password = match hash_password(credentials.password.clone()) {
Ok(hash) => hash,
Err(_) => {
return Err(Json(ErrorResponse {
status: Status::InternalServerError.code,
message: "Something went wrong, please try again later".to_string(),
}));
}
};
let user = match repo
.update_user(
uuid,
Some(&credentials.username),
Some(&credentials.email),
Some(&hashed_password),
)
.await
{
Ok(Some(user)) => user,
Ok(None) => {
return Err(Json(ErrorResponse {
status: Status::NotFound.code,
message: "User not found".to_string(),
}));
}
Err(_) => {
return Err(Json(ErrorResponse {
status: Status::InternalServerError.code,
message: "Something went wrong, please try again later".to_string(),
}));
}
};
Ok(Json(user))
}
/// Deletes a user by ID (requires authentication).
#[delete("/delete/<id>")]
pub async fn delete_user(
_auth: AuthClaims,
repo: &State<Arc<UserRepository>>,
id: &str,
) -> Result<Json<SuccessResponse>, Json<ErrorResponse>> {
let uuid = match Uuid::parse_str(id) {
Ok(uuid) => uuid,
Err(_) => {
return Err(Json(ErrorResponse {
status: Status::BadRequest.code,
message: "Invalid user ID format".to_string(),
}));
}
};
match repo.delete_user(uuid).await {
Ok(Some(_)) => Ok(Json(SuccessResponse {
status: Status::Ok.code,
message: "User deleted successfully".to_string(),
})),
Ok(None) => {
return Err(Json(ErrorResponse {
status: Status::NotFound.code,
message: "User not found".to_string(),
}));
}
Err(_) => {
return Err(Json(ErrorResponse {
status: Status::InternalServerError.code,
message: "Something went wrong, please try again later".to_string(),
}));
}
}
}
/// Collects all user-related routes for mounting.
pub fn user_routes() -> Vec<rocket::Route> {
routes![
register,
login,
logout,
get_user,
get_user_by_email,
update_user,
delete_user
]
}
"#;