feat: added redis session
This commit is contained in:
parent
0518eaa6ab
commit
60132e046d
|
|
@ -335,6 +335,7 @@ dependencies = [
|
||||||
"dotenv",
|
"dotenv",
|
||||||
"gotrue",
|
"gotrue",
|
||||||
"gotrue-entity",
|
"gotrue-entity",
|
||||||
|
"redis",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ askama = "0.12.1"
|
||||||
axum-extra = { version = "0.8.0", features = ["cookie"] }
|
axum-extra = { version = "0.8.0", features = ["cookie"] }
|
||||||
serde = { version = "1.0.188", features = ["derive"] }
|
serde = { version = "1.0.188", features = ["derive"] }
|
||||||
serde_json = "1.0.107"
|
serde_json = "1.0.107"
|
||||||
|
redis = { version = "0.23.3", features = [ "aio", "tokio-comp", "connection-manager"] }
|
||||||
|
|
||||||
# local dependencies
|
# local dependencies
|
||||||
gotrue = { path = "../libs/gotrue" }
|
gotrue = { path = "../libs/gotrue" }
|
||||||
|
|
|
||||||
|
|
@ -1 +1,2 @@
|
||||||
GOTRUE_URL=http://localhost:9998
|
GOTRUE_URL=http://localhost:9998
|
||||||
|
REDIS_URL=redis://localhost:6380
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ mod access_token;
|
||||||
mod error;
|
mod error;
|
||||||
mod models;
|
mod models;
|
||||||
mod response;
|
mod response;
|
||||||
|
mod session;
|
||||||
mod templates;
|
mod templates;
|
||||||
mod web_api;
|
mod web_api;
|
||||||
mod web_app;
|
mod web_app;
|
||||||
|
|
@ -17,8 +18,18 @@ async fn main() {
|
||||||
reqwest::Client::new(),
|
reqwest::Client::new(),
|
||||||
&std::env::var("GOTRUE_URL").unwrap_or("http://gotrue:9999".to_string()),
|
&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_app_router = web_app::router().with_state(state.clone());
|
||||||
let web_api_router = web_api::router().with_state(state);
|
let web_api_router = web_api::router().with_state(state);
|
||||||
|
|
@ -37,4 +48,5 @@ async fn main() {
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
pub gotrue_client: gotrue::api::Client,
|
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">
|
<html lang="en">
|
||||||
<body>
|
<body>
|
||||||
<h1>User List</h1>
|
<h1>User List</h1>
|
||||||
|
|
||||||
|
<!-- Button to create new user -->
|
||||||
|
<button id="createUserBtn">Create User</button>
|
||||||
|
|
||||||
<ul id="userList"></ul>
|
<ul id="userList"></ul>
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
|
|
@ -14,7 +18,6 @@
|
||||||
|
|
||||||
{% for user in users %}
|
{% for user in users %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ user.id|escape }}</td>
|
|
||||||
<td>{{ user.email|escape }}</td>
|
<td>{{ user.email|escape }}</td>
|
||||||
<td>{{ user.created_at|escape }}</td>
|
<td>{{ user.created_at|escape }}</td>
|
||||||
<td><a href="/web/admin/users/{{ user.id }}" class="btn">Go</a></td>
|
<td><a href="/web/admin/users/{{ user.id }}" class="btn">Go</a></td>
|
||||||
|
|
@ -22,6 +25,32 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue