nxtgauge-backend-rust/crates/cache/src/rate_limit.rs

53 lines
1.9 KiB
Rust
Raw Normal View History

//! Generic sliding-window rate limiter.
//!
//! Key pattern: `rate:{namespace}:{identifier}`
//! Returns `Ok(true)` if the request is allowed, `Ok(false)` if rate-limited.
use redis::AsyncCommands;
use crate::RedisPool;
/// Check + increment a rate-limit counter.
///
/// * `namespace` e.g. `"login"`, `"register"`, `"lead"`
/// * `identifier` e.g. email, IP, user_id
/// * `max` maximum requests allowed in `window_secs`
/// * `window_secs` sliding window length in seconds
///
/// Returns `Ok(true)` = allowed, `Ok(false)` = blocked.
pub async fn check(
redis: &mut RedisPool,
namespace: &str,
identifier: &str,
max: i64,
window_secs: i64,
) -> Result<bool, redis::RedisError> {
let key = format!("rate:{namespace}:{identifier}");
let count: i64 = redis.incr(&key, 1i64).await?;
if count == 1 {
redis.expire::<_, ()>(&key, window_secs).await?;
}
Ok(count <= max)
}
/// Convenience wrappers ───────────────────────────────────────────────────────
/// Register: max 10 per hour per email
pub async fn check_register(redis: &mut RedisPool, email: &str) -> Result<bool, redis::RedisError> {
check(redis, "register", email, 10, 3_600).await
}
/// Login: max 10 attempts per 15 min per email
pub async fn check_login(redis: &mut RedisPool, email: &str) -> Result<bool, redis::RedisError> {
check(redis, "login", email, 10, 900).await
}
/// Lead request: max 5 per hour per professional
pub async fn check_lead(redis: &mut RedisPool, professional_id: &str) -> Result<bool, redis::RedisError> {
check(redis, "lead", professional_id, 5, 3_600).await
}
/// Job post: max 20 per hour per company
pub async fn check_job_post(redis: &mut RedisPool, company_id: &str) -> Result<bool, redis::RedisError> {
check(redis, "job_post", company_id, 20, 3_600).await
}