feat: Enhance validation and permissions handling across controllers

- Added `ValidatedJson` extractor for automatic JSON validation in `events_controller.rs`, `testimonials_controller.rs`, `mentors_controller.rs`, `gacha_items_controller.rs`, and `hackathon_controller.rs`.
- Replaced manual permission checks with `require_permissions!` and `require_auth!` macros in relevant controllers to streamline permission handling.
- Introduced `sanitization` utilities in `sanitization.rs` for improved input sanitization.
- Added `permission_macros.rs` to encapsulate permission checking logic and reduce boilerplate.
- Updated dependencies in `Cargo.toml` to include `serde_json` and `validator`.
- Implemented error handling improvements in `notification_service.rs` for better response management.
This commit is contained in:
MythEclipse
2025-10-28 14:04:41 +07:00
parent d4a6c4c9ea
commit b9a51ce6cc
17 changed files with 551 additions and 225 deletions
+2
View File
@@ -10,6 +10,8 @@ log.workspace = true
axum.workspace = true
tokio.workspace = true
serde.workspace = true
serde_json.workspace = true
validator.workspace = true
argon2.workspace = true
lettre.workspace = true
chrono.workspace = true
+4
View File
@@ -3,12 +3,16 @@
//! This module provides utilities for initializing and running an Axum web server
//! with SurrealDB connections for both WebSocket and in-memory databases.
pub mod validated_json;
use crate::{surrealdb_init_mem, surrealdb_init_ws, SurrealMemClient, SurrealWsClient};
use axum::{Router, serve};
use std::{future::Future, net::SocketAddr};
use tokio::net::TcpListener;
use crate::environment::ENV;
pub use validated_json::ValidatedJson;
/// Initialize and start the Axum server with SurrealDB connections.
///
/// This function sets up both WebSocket and in-memory SurrealDB connections,
+111
View File
@@ -0,0 +1,111 @@
//! Custom extractor for automatic JSON validation and sanitization
//!
//! This extractor automatically validates request payloads using the validator crate
//! and returns appropriate error responses if validation fails.
use axum::{
extract::{rejection::JsonRejection, FromRequest, Request},
http::StatusCode,
response::{IntoResponse, Response},
Json,
};
use serde::de::DeserializeOwned;
use serde_json;
use validator::Validate;
/// Custom extractor that automatically validates JSON payloads
///
/// # Example
/// ```rust
/// use validated_json::ValidatedJson;
/// use serde::Deserialize;
/// use validator::Validate;
///
/// #[derive(Deserialize, Validate)]
/// struct CreateUserRequest {
/// #[validate(email)]
/// email: String,
/// #[validate(length(min = 8))]
/// password: String,
/// }
///
/// async fn create_user(
/// ValidatedJson(payload): ValidatedJson<CreateUserRequest>
/// ) -> Response {
/// // payload is already validated
/// // ... your logic here
/// }
/// ```
pub struct ValidatedJson<T>(pub T);
impl<T, S> FromRequest<S> for ValidatedJson<T>
where
T: DeserializeOwned + Validate + 'static,
S: Send + Sync,
Json<T>: FromRequest<S, Rejection = JsonRejection>,
{
type Rejection = Response;
async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
// First, extract JSON
let Json(value) = match Json::<T>::from_request(req, state).await {
Ok(value) => value,
Err(rejection) => {
let error_message = format!("Invalid JSON payload: {}", rejection);
return Err((
StatusCode::BAD_REQUEST,
Json(serde_json::json!({
"error": error_message,
"version": env!("CARGO_PKG_VERSION"),
})),
)
.into_response());
}
};
// Then, validate it
if let Err(errors) = value.validate() {
let error_messages: Vec<String> = errors
.field_errors()
.iter()
.flat_map(|(field, errors)| {
errors.iter().map(move |error| {
format!(
"{}: {}",
field,
error.message.as_ref().map(|m| m.to_string()).unwrap_or_else(|| error.code.to_string())
)
})
})
.collect();
return Err((
StatusCode::BAD_REQUEST,
Json(serde_json::json!({
"error": "Validation failed",
"details": error_messages,
"version": env!("CARGO_PKG_VERSION"),
})),
)
.into_response());
}
Ok(ValidatedJson(value))
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde::Deserialize;
#[derive(Debug, Deserialize, Validate)]
struct TestPayload {
#[validate(email)]
email: String,
#[validate(length(min = 8))]
password: String,
}
// Note: Full integration tests should be done at the application level
}
+1 -1
View File
@@ -27,7 +27,7 @@ pub mod services;
pub mod surrealdb;
pub use argon::{hash_password, verify_password};
pub use axum::axum_init;
pub use axum::{axum_init, ValidatedJson};
pub use environment::{ENV, Env};
pub use imphnen_entities::{
MessageResponseDto,