feat: added redis session

This commit is contained in:
Fu Zi Xiang 2023-10-11 11:45:52 +08:00
parent 0518eaa6ab
commit 60132e046d
No known key found for this signature in database
GPG Key ID: 7AE0884D237CEE16
6 changed files with 151 additions and 3 deletions

1
Cargo.lock generated
View File

@ -335,6 +335,7 @@ dependencies = [
"dotenv",
"gotrue",
"gotrue-entity",
"redis",
"reqwest",
"serde",
"serde_json",

View File

@ -13,6 +13,7 @@ askama = "0.12.1"
axum-extra = { version = "0.8.0", features = ["cookie"] }
serde = { version = "1.0.188", features = ["derive"] }
serde_json = "1.0.107"
redis = { version = "0.23.3", features = [ "aio", "tokio-comp", "connection-manager"] }
# local dependencies
gotrue = { path = "../libs/gotrue" }

View File

@ -1 +1,2 @@
GOTRUE_URL=http://localhost:9998
REDIS_URL=redis://localhost:6380

View File

@ -2,6 +2,7 @@ mod access_token;
mod error;
mod models;
mod response;
mod session;
mod templates;
mod web_api;
mod web_app;
@ -17,8 +18,18 @@ async fn main() {
reqwest::Client::new(),
&std::env::var("GOTRUE_URL").unwrap_or("http://gotrue:9999".to_string()),
);
let redis_client =
redis::Client::open(std::env::var("REDIS_URL").unwrap_or("redis://redis:6379".to_string()))
.unwrap()
.get_tokio_connection_manager()
.await
.unwrap();
let session_store = session::SessionStorage::new(redis_client);
let state = AppState { gotrue_client };
let state = AppState {
gotrue_client,
session_store,
};
let web_app_router = web_app::router().with_state(state.clone());
let web_api_router = web_api::router().with_state(state);
@ -37,4 +48,5 @@ async fn main() {
#[derive(Clone)]
pub struct AppState {
pub gotrue_client: gotrue::api::Client,
pub session_store: session::SessionStorage,
}

View File

@ -0,0 +1,104 @@
use redis::{aio::ConnectionManager, AsyncCommands, FromRedisValue, ToRedisArgs};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
static SESSION_EXPIRATION: usize = 60 * 60 * 24; // 1 day
#[derive(Clone)]
pub struct SessionStorage {
redis_client: ConnectionManager,
}
fn session_id_key(session_id: &str) -> String {
format!("web::session::{}", session_id)
}
impl SessionStorage {
pub fn new(redis_client: ConnectionManager) -> Self {
Self { redis_client }
}
pub async fn get_user_session(&self, session_id: &str) -> Option<UserSession> {
let key = session_id_key(session_id);
let s: Result<UserSession, redis::RedisError> = self.redis_client.clone().get(&key).await;
match s {
Ok(s) => Some(s),
Err(e) => {
println!("redis error: {:?}", e);
None
},
}
}
pub async fn put_user_session(&self, user_session: UserSession) -> redis::RedisResult<()> {
let key = session_id_key(&user_session.session_id);
self
.redis_client
.clone()
.set_options(
key,
user_session,
redis::SetOptions::default().with_expiration(redis::SetExpiry::EX(SESSION_EXPIRATION)),
)
.await
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct UserSession {
session_id: String,
access_token: String,
refresh_token: String,
}
impl UserSession {
pub fn new(session_id: String, access_token: String, refresh_token: String) -> Self {
Self {
session_id,
access_token,
refresh_token,
}
}
}
impl ToRedisArgs for UserSession {
fn write_redis_args<W>(&self, out: &mut W)
where
W: ?Sized + redis::RedisWrite,
{
let s = serde_json::to_string(self).unwrap();
out.write_arg(s.as_bytes());
}
}
impl FromRedisValue for UserSession {
fn from_redis_value(v: &redis::Value) -> redis::RedisResult<Self> {
let bytes = expect_redis_value_data(v)?;
expect_redis_json_bytes(bytes)
}
}
fn expect_redis_json_bytes<T>(v: &[u8]) -> redis::RedisResult<T>
where
T: DeserializeOwned,
{
let res: Result<T, serde_json::Error> = serde_json::from_slice(v);
match res {
Ok(v) => Ok(v),
Err(e) => Err(redis::RedisError::from((
redis::ErrorKind::TypeError,
"redis data json deserialization failed!",
e.to_string(),
))),
}
}
fn expect_redis_value_data(v: &redis::Value) -> redis::RedisResult<&[u8]> {
match v {
redis::Value::Data(ref bytes) => Ok(bytes),
x => Err(redis::RedisError::from((
redis::ErrorKind::TypeError,
"unexpected value from redis",
format!("redis value is not data: {:?}", x),
))),
}
}

View File

@ -2,6 +2,10 @@
<html lang="en">
<body>
<h1>User List</h1>
<!-- Button to create new user -->
<button id="createUserBtn">Create User</button>
<ul id="userList"></ul>
<table>
@ -14,7 +18,6 @@
{% for user in users %}
<tr>
<td>{{ user.id|escape }}</td>
<td>{{ user.email|escape }}</td>
<td>{{ user.created_at|escape }}</td>
<td><a href="/web/admin/users/{{ user.id }}" class="btn">Go</a></td>
@ -22,6 +25,32 @@
{% endfor %}
</table>
<script></script>
<script>
document.getElementById('createUserBtn').addEventListener('click', function() {
// Get the email from the user
const email = prompt('Please enter the new user email:');
// TODO: Validate the email, if valid, send to server and update UI
if (email) {
// Assuming you will use fetch API or another method to send the data to the server
// Example:
// fetch('/createUser', {
// method: 'POST',
// body: JSON.stringify({ email: email }),
// headers: {
// 'Content-Type': 'application/json',
// }
// })
// .then(response => response.json())
// .then(data => {
// // Update the UI as needed, e.g., add a new row to the table
// console.log('User created:', data);
// })
// .catch((error) => {
// console.error('Error:', error);
// });
}
});
</script>
</body>
</html>