1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
//! An [async-session](https://github.com/http-rs/async-session) backend implemented
//! using [sea-orm](https://github.com/SeaQL/sea-orm), heavily inspired by
//! [async-sqlx-session](https://github.com/jbr/async-sqlx-session).
//!
//! # Basic usage
//!
//! In the following example we create a [`DatabaseSessionStore`], which implements
//! the [`SessionStore`] trait from [`async_session`].
//!
//! ```rust,no_run
//! use async_sea_orm_session::migration::Migrator;
//! use async_sea_orm_session::DatabaseSessionStore;
//! use sea_orm::{Database, DatabaseConnection};
//! use sea_orm_migration::MigratorTrait;
//!
//! #[tokio::main]
//! async fn main() -> Result<(), sea_orm::DbErr> {
//!     // Create a sea_orm::DatabaseConnection in the usual way.
//!     let db: DatabaseConnection =
//!         Database::connect("protocol://username:password@host/database").await?;
//!
//!     // Run the async_sea_orm_session migration to create the session table.
//!     Migrator::up(&db, None).await?;
//!
//!     // Finally create a DatabaseSessionStore that implements SessionStore.
//!     let store = DatabaseSessionStore::new(db);
//!     Ok(())
//! }
//! ```
//!
//! # Examples
//!
//! For examples see the README in the [repository](https://github.com/dcchut/async-sea-orm-session).
//!
//! # License
//!
//! Licensed under either of
//! * Apache License, Version 2.0
//! ([LICENSE-APACHE](LICENSE-APACHE) or <http://www.apache.org/licenses/LICENSE-2.0>)
//! * MIT license
//! ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
//!
//! at your option.
#![cfg_attr(docsrs, feature(doc_cfg))]
use async_session::{async_trait, serde_json, SessionStore};
use sea_orm::prelude::*;
use sea_orm::{sea_query, ConnectionTrait, DatabaseConnection, StatementBuilder};
use sea_query::OnConflict;

#[cfg(feature = "migration")]
#[cfg_attr(docsrs, doc(cfg(feature = "migration")))]
pub mod migration;
pub mod prelude;
mod sessions;

use sessions::Entity as Session;

#[derive(Clone, Debug)]
pub struct DatabaseSessionStore {
    connection: DatabaseConnection,
}

impl DatabaseSessionStore {
    /// Create a new [`DatabaseSessionStore`] from the given [`DatabaseConnection`].
    ///
    /// # Example
    /// ```rust
    /// use async_sea_orm_session::DatabaseSessionStore;
    /// use sea_orm::{Database, DatabaseConnection};
    /// # async fn doctest() -> Result<(), Box<dyn std::error::Error>> {
    ///
    /// let db: DatabaseConnection =
    ///     Database::connect("protocol://username:password@host/database").await?;
    ///
    /// // Make a `DatabaseSessionStore` which reuses the underlying connection pool:
    /// let store = DatabaseSessionStore::new(db.clone());
    ///
    /// // Alternatively if you don't mind moving `db` you can do:
    /// let store = DatabaseSessionStore::new(db);
    /// # Ok(())
    /// # }
    /// ```
    pub fn new(connection: DatabaseConnection) -> DatabaseSessionStore {
        Self { connection }
    }
}

#[async_trait]
impl SessionStore for DatabaseSessionStore {
    async fn load_session(
        &self,
        cookie_value: String,
    ) -> async_session::Result<Option<async_session::Session>> {
        let id = async_session::Session::id_from_cookie_value(&cookie_value)?;
        Ok(Session::find_by_id(id)
            .one(&self.connection)
            .await?
            .map(|m| serde_json::from_value(m.session))
            .transpose()?)
    }

    async fn store_session(
        &self,
        session: async_session::Session,
    ) -> async_session::Result<Option<String>> {
        let stmt = StatementBuilder::build(
            sea_query::Query::insert()
                .into_table(Session)
                .columns(vec![sessions::Column::Id, sessions::Column::Session])
                .values(vec![
                    session.id().into(),
                    serde_json::to_value(&session)?.into(),
                ])?
                .on_conflict(
                    OnConflict::column(sessions::Column::Id)
                        .update_columns([sessions::Column::Session])
                        .to_owned(),
                ),
            &self.connection.get_database_backend(),
        );

        self.connection.execute(stmt).await?;
        Ok(session.into_cookie_value())
    }

    async fn destroy_session(&self, session: async_session::Session) -> async_session::Result {
        Session::delete_by_id(session.id().to_string())
            .exec(&self.connection)
            .await?;
        Ok(())
    }

    async fn clear_store(&self) -> async_session::Result {
        Session::delete_many().exec(&self.connection).await?;
        Ok(())
    }
}