feat: added redis session
This commit is contained in:
parent
0518eaa6ab
commit
60132e046d
|
|
@ -335,6 +335,7 @@ dependencies = [
|
|||
"dotenv",
|
||||
"gotrue",
|
||||
"gotrue-entity",
|
||||
"redis",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
|
|
|||
|
|
@ -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" }
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
GOTRUE_URL=http://localhost:9998
|
||||
REDIS_URL=redis://localhost:6380
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in New Issue