1use chrono::{Duration, NaiveDateTime, Utc};
14use diesel::prelude::*;
15use diesel::result::Error;
16use rand::RngExt;
17use rand::distr::Alphanumeric;
18
19use crate::schema::sessions;
20
21pub const SESSION_TTL_DAYS: i64 = 7;
24
25#[derive(Queryable, Identifiable, Debug, Clone)]
27#[diesel(table_name = sessions)]
28pub struct Session {
29 pub id: String,
31 pub owner: String,
33 pub method: String,
35 pub created_at: NaiveDateTime,
37 pub expires_at: NaiveDateTime,
39}
40
41#[derive(Insertable)]
42#[diesel(table_name = sessions)]
43struct NewSession<'a> {
44 id: &'a str,
45 owner: &'a str,
46 method: &'a str,
47 expires_at: NaiveDateTime,
48}
49
50impl Session {
51 pub fn open(connection: &mut PgConnection, owner: &str, method: &str) -> Result<String, Error> {
55 let _ = Self::prune_expired(connection);
56 let id: String = rand::rng()
58 .sample_iter(&Alphanumeric)
59 .take(48)
60 .map(char::from)
61 .collect();
62 let expires_at = (Utc::now() + Duration::days(SESSION_TTL_DAYS)).naive_utc();
63 diesel::insert_into(sessions::table)
64 .values(NewSession {
65 id: &id,
66 owner,
67 method,
68 expires_at,
69 })
70 .execute(connection)?;
71 Ok(id)
72 }
73
74 pub fn resolve_owner(connection: &mut PgConnection, id: &str) -> Option<String> {
76 use crate::schema::sessions::dsl;
77 dsl::sessions
78 .filter(dsl::id.eq(id))
79 .filter(dsl::expires_at.gt(Utc::now().naive_utc()))
80 .select(dsl::owner)
81 .first::<String>(connection)
82 .optional()
83 .ok()
84 .flatten()
85 }
86
87 pub fn revoke(connection: &mut PgConnection, id: &str) -> Result<(), Error> {
89 use crate::schema::sessions::dsl;
90 diesel::delete(dsl::sessions.filter(dsl::id.eq(id)))
91 .execute(connection)
92 .map(|_| ())
93 }
94
95 pub fn revoke_all_for(connection: &mut PgConnection, owner: &str) -> Result<usize, Error> {
97 use crate::schema::sessions::dsl;
98 diesel::delete(dsl::sessions.filter(dsl::owner.eq(owner))).execute(connection)
99 }
100
101 pub fn active(connection: &mut PgConnection) -> Result<Vec<Self>, Error> {
103 use crate::schema::sessions::dsl;
104 dsl::sessions
105 .filter(dsl::expires_at.gt(Utc::now().naive_utc()))
106 .order(dsl::created_at.desc())
107 .get_results(connection)
108 }
109
110 pub fn prune_expired(connection: &mut PgConnection) -> Result<usize, Error> {
112 use crate::schema::sessions::dsl;
113 diesel::delete(dsl::sessions.filter(dsl::expires_at.le(Utc::now().naive_utc())))
114 .execute(connection)
115 }
116}