use mongodb::bson::{DateTime as BsonDateTime, doc};
use mongodb::options::IndexOptions;
use mongodb::{Client, Collection, IndexModel};
use serde::{Deserialize, Serialize};
use crate::{Result, Session, SessionStore, async_trait};
const DEFAULT_COLLECTION: &str = "saysion_sessions";
#[derive(Clone, Debug)]
pub struct MongoDbStore {
client: Client,
db: String,
coll_name: String,
}
#[derive(Serialize, Deserialize)]
struct SessionDoc {
#[serde(rename = "_id")]
id: String,
expires_at: Option<BsonDateTime>,
session: String,
}
impl MongoDbStore {
pub fn new(client: Client, db: impl Into<String>) -> Self {
Self {
client,
db: db.into(),
coll_name: DEFAULT_COLLECTION.to_string(),
}
}
pub async fn from_uri(uri: &str, db: impl Into<String>) -> Result<Self> {
Ok(Self::new(Client::with_uri_str(uri).await?, db))
}
pub fn with_collection(mut self, coll: impl Into<String>) -> Self {
self.coll_name = coll.into();
self
}
fn coll(&self) -> Collection<SessionDoc> {
self.client.database(&self.db).collection(&self.coll_name)
}
pub async fn initialize(&self) -> Result {
let model = IndexModel::builder()
.keys(doc! { "expires_at": 1 })
.options(
IndexOptions::builder()
.expire_after(std::time::Duration::from_secs(0))
.build(),
)
.build();
self.coll().create_index(model).await?;
Ok(())
}
pub async fn count(&self) -> Result<u64> {
Ok(self.coll().count_documents(doc! {}).await?)
}
}
#[async_trait]
impl SessionStore for MongoDbStore {
async fn load_session(&self, cookie_value: String) -> Result<Option<Session>> {
let id = Session::id_from_cookie_value(&cookie_value)?;
tracing::debug!("loading session by id `{}`", id);
let found = self.coll().find_one(doc! { "_id": &id }).await?;
Ok(match found {
None => None,
Some(d) => serde_json::from_str::<Session>(&d.session)?.validate(),
})
}
async fn store_session(&self, session: Session) -> Result<Option<String>> {
let id = session.id().to_string();
tracing::debug!("storing session by id `{}`", id);
let json = serde_json::to_string(&session)?;
let expires_at = session
.expiry()
.map(|e| BsonDateTime::from_millis(e.unix_timestamp() * 1000));
let doc_value = SessionDoc {
id: id.clone(),
expires_at,
session: json,
};
self.coll()
.replace_one(doc! { "_id": &id }, &doc_value)
.upsert(true)
.await?;
session.reset_data_changed();
Ok(session.into_cookie_value())
}
async fn destroy_session(&self, session: Session) -> Result {
tracing::debug!("destroying session by id `{}`", session.id());
self.coll().delete_one(doc! { "_id": session.id() }).await?;
Ok(())
}
async fn clear_store(&self) -> Result {
tracing::debug!("clearing mongodb store");
self.coll().delete_many(doc! {}).await?;
Ok(())
}
}