- add: roles to user
- add: register calls for auth server - add: user lookup by email - add: start of password reset - add: Cache trait to allow redis cache mocking
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
use crate::db::Database;
|
||||
use crate::database::{CreateUserRequest, CreateUserResponse, GetUserRequest, GetUserByUsernameRequest, GetUserResponse};
|
||||
use crate::database::{CreateUserRequest, CreateUserResponse, GetUserRequest, GetUserByUsernameRequest, GetUserByEmailRequest, GetUserResponse};
|
||||
use tonic::{Request, Response, Status};
|
||||
|
||||
use crate::database::database_service_server::{DatabaseService};
|
||||
@@ -26,9 +26,24 @@ impl DatabaseService for MyDatabaseService {
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
hashed_password: user.hashed_password,
|
||||
roles: user.roles,
|
||||
}))
|
||||
}
|
||||
|
||||
async fn create_user(
|
||||
&self,
|
||||
request: Request<CreateUserRequest>,
|
||||
) -> Result<Response<CreateUserResponse>, Status> {
|
||||
let req = request.into_inner();
|
||||
|
||||
let user_id = self.db.users_service.create_user(&req.username, &req.email, &req.hashed_password)
|
||||
.await
|
||||
.map_err(|_| Status::internal("Failed to create user"))?;
|
||||
|
||||
// Return the newly created user ID
|
||||
Ok(Response::new(CreateUserResponse { user_id }))
|
||||
}
|
||||
|
||||
async fn get_user_by_username(
|
||||
&self,
|
||||
request: Request<GetUserByUsernameRequest>,
|
||||
@@ -44,20 +59,26 @@ impl DatabaseService for MyDatabaseService {
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
hashed_password: user.hashed_password,
|
||||
roles: user.roles,
|
||||
}))
|
||||
}
|
||||
|
||||
async fn create_user(
|
||||
async fn get_user_by_email(
|
||||
&self,
|
||||
request: Request<CreateUserRequest>,
|
||||
) -> Result<Response<CreateUserResponse>, Status> {
|
||||
request: Request<GetUserByEmailRequest>,
|
||||
) -> Result<Response<GetUserResponse>, Status> {
|
||||
let req = request.into_inner();
|
||||
|
||||
let user_id = self.db.users_service.create_user(&req.username, &req.email, &req.hashed_password)
|
||||
let user = self.db.users_service.get_user_by_email(&req.email)
|
||||
.await
|
||||
.map_err(|_| Status::internal("Failed to create user"))?;
|
||||
.map_err(|_| Status::not_found("User not found"))?;
|
||||
|
||||
// Return the newly created user ID
|
||||
Ok(Response::new(CreateUserResponse { user_id: user_id }))
|
||||
Ok(Response::new(GetUserResponse {
|
||||
user_id: user.id,
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
hashed_password: user.hashed_password,
|
||||
roles: user.roles,
|
||||
}))
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,39 @@
|
||||
use deadpool_redis::{Config, Pool, Runtime};
|
||||
use redis::{AsyncCommands, RedisError};
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use async_trait::async_trait;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
#[async_trait]
|
||||
pub trait Cache {
|
||||
fn new(redis_url: &str) -> Self;
|
||||
async fn set<T: Serialize + Send + Sync>(
|
||||
&self,
|
||||
key: &String,
|
||||
value: &T,
|
||||
ttl: u64,
|
||||
) -> Result<(), redis::RedisError>;
|
||||
|
||||
async fn get<T: for<'de> serde::Deserialize<'de> + Send + Sync>(
|
||||
&self,
|
||||
key: &String,
|
||||
) -> Result<Option<T>, redis::RedisError>;
|
||||
|
||||
}
|
||||
|
||||
pub struct RedisCache {
|
||||
pub pool: Pool,
|
||||
}
|
||||
|
||||
impl Cache for RedisCache {
|
||||
fn new(redis_url: &str) -> Self {
|
||||
impl RedisCache {
|
||||
pub fn new(redis_url: &str) -> Self {
|
||||
let cfg = Config::from_url(redis_url);
|
||||
let pool = cfg.create_pool(Some(Runtime::Tokio1)).expect("Failed to create Redis pool");
|
||||
RedisCache { pool }
|
||||
}
|
||||
}
|
||||
|
||||
async fn set<T: Serialize + std::marker::Send + std::marker::Sync>(
|
||||
#[async_trait]
|
||||
impl Cache for RedisCache {
|
||||
async fn set<T: Serialize + Send + Sync>(
|
||||
&self,
|
||||
key: &String,
|
||||
value: &T,
|
||||
@@ -41,7 +56,7 @@ impl Cache for RedisCache {
|
||||
conn.set_ex(key, serialized_value, ttl).await
|
||||
}
|
||||
|
||||
async fn get<T: DeserializeOwned>(&self, key: &String) -> Result<Option<T>, redis::RedisError> {
|
||||
async fn get<T: for<'de> Deserialize<'de> + Send + Sync>(&self, key: &String) -> Result<Option<T>, redis::RedisError> {
|
||||
let mut conn = self.pool.get().await
|
||||
.map_err(|err| {
|
||||
redis::RedisError::from((
|
||||
|
||||
@@ -2,7 +2,7 @@ use sqlx::Error;
|
||||
use sqlx::PgPool;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use std::sync::Arc;
|
||||
use crate::redis_cache::RedisCache;
|
||||
use crate::redis_cache::{Cache, RedisCache};
|
||||
use tracing::{debug, error};
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ pub struct User {
|
||||
pub username: String,
|
||||
pub email: String,
|
||||
pub hashed_password: String,
|
||||
pub roles: Vec<String>,
|
||||
}
|
||||
|
||||
pub struct UsersService {
|
||||
@@ -50,13 +51,20 @@ impl UsersService {
|
||||
}
|
||||
|
||||
// Fetch from PostgreSQL
|
||||
let user = sqlx::query_as!(
|
||||
User,
|
||||
"SELECT id, username, email, hashed_password FROM users WHERE id = $1",
|
||||
let row = sqlx::query!(
|
||||
"SELECT id, username, email, hashed_password, roles FROM users WHERE id = $1",
|
||||
user_id
|
||||
)
|
||||
.fetch_one(&self.pool)
|
||||
.await?;
|
||||
|
||||
let user = User {
|
||||
id: row.id,
|
||||
username: row.username,
|
||||
email: row.email,
|
||||
hashed_password: row.hashed_password,
|
||||
roles: row.roles.unwrap_or_default(),
|
||||
};
|
||||
|
||||
// Store result in Redis
|
||||
self.cache
|
||||
@@ -74,14 +82,21 @@ impl UsersService {
|
||||
}
|
||||
|
||||
// Fetch from PostgreSQL
|
||||
let user = sqlx::query_as!(
|
||||
User,
|
||||
"SELECT id, username, email, hashed_password FROM users WHERE username = $1",
|
||||
let row = sqlx::query!(
|
||||
"SELECT id, username, email, hashed_password, roles FROM users WHERE username = $1",
|
||||
username
|
||||
)
|
||||
.fetch_one(&self.pool)
|
||||
.await?;
|
||||
|
||||
let user = User {
|
||||
id: row.id,
|
||||
username: row.username,
|
||||
email: row.email,
|
||||
hashed_password: row.hashed_password,
|
||||
roles: row.roles.unwrap_or_default(),
|
||||
};
|
||||
|
||||
// Store result in Redis
|
||||
self.cache
|
||||
.set(&format!("user_by_username:{}", username), &user, 3600)
|
||||
@@ -91,6 +106,37 @@ impl UsersService {
|
||||
Ok(user)
|
||||
}
|
||||
|
||||
pub async fn get_user_by_email(&self, email: &str) -> Result<User, Error> {
|
||||
// Check Redis cache first
|
||||
if let Ok(Some(cached_user)) = self.cache.get::<User>(&format!("user_by_email:{}", email)).await {
|
||||
return Ok(cached_user);
|
||||
}
|
||||
|
||||
// Fetch from PostgreSQL
|
||||
let row = sqlx::query!(
|
||||
"SELECT id, username, email, hashed_password, roles FROM users WHERE email = $1",
|
||||
email
|
||||
)
|
||||
.fetch_one(&self.pool)
|
||||
.await?;
|
||||
|
||||
let user = User {
|
||||
id: row.id,
|
||||
username: row.username,
|
||||
email: row.email,
|
||||
hashed_password: row.hashed_password,
|
||||
roles: row.roles.unwrap_or_default(),
|
||||
};
|
||||
|
||||
// Store result in Redis
|
||||
self.cache
|
||||
.set(&format!("user_by_email:{}", email), &user, 3600)
|
||||
.await
|
||||
.unwrap_or_else(|err| eprintln!("Failed to cache user: {:?}", err));
|
||||
|
||||
Ok(user)
|
||||
}
|
||||
|
||||
pub async fn update_user_email(&self, user_id: i32, new_email: &str) -> Result<(), Error> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
|
||||
Reference in New Issue
Block a user