1mod session_data;
2
3pub use ft_sys_shared::TRACKER_KEY;
4pub use session_data::SessionData;
5
6#[derive(Clone, Debug)]
7pub struct SessionID(pub String);
8
9impl SessionID {
10 pub fn create(
13 conn: &mut ft_sdk::Connection,
14 user_id: Option<ft_sdk::auth::UserId>,
15 data: Option<serde_json::Value>,
16 ) -> Result<Self, ft_sdk::Error> {
17 use diesel::prelude::*;
18 use ft_sdk::schema::fastn_session;
19
20 let session_id = ft_sdk::utils::uuid_v8();
21
22 let data = match data {
23 Some(d) => serde_json::to_string(&d)?,
24 None => "{}".to_string(),
25 };
26
27 let user_id = user_id.map(|u| u.0);
28
29 diesel::insert_into(fastn_session::table)
30 .values((
31 fastn_session::id.eq(&session_id),
32 fastn_session::uid.eq(user_id),
33 fastn_session::created_at.eq(ft_sdk::env::now()),
34 fastn_session::updated_at.eq(ft_sdk::env::now()),
35 fastn_session::data.eq(data),
36 ))
37 .execute(conn)?;
38
39 Ok(Self(session_id))
40 }
41
42 pub fn from_string<S: AsRef<str>>(s: S) -> Self {
43 Self(s.as_ref().to_string())
44 }
45
46 pub fn set_user_id(
48 &self,
49 conn: &mut ft_sdk::Connection,
50 user_id: ft_sdk::auth::UserId,
51 ) -> Result<SessionID, diesel::result::Error> {
52 use diesel::prelude::*;
53 use ft_sdk::schema::fastn_session;
54
55 let affected =
56 diesel::update(fastn_session::table.filter(fastn_session::id.eq(self.0.as_str())))
57 .set(fastn_session::uid.eq(Some(user_id.0)))
59 .execute(conn)?;
60
61 assert_eq!(
62 affected, 1,
63 r#"Expected to update exactly one session. SessionID is unique so
64there can't be more than one. Zero is not possible if the SessionID can only be constructed
65using `SessionID::new`"#
66 );
67
68 Ok(self.clone())
69 }
70
71 pub fn data(
75 &self,
76 conn: &mut ft_sdk::Connection,
77 ) -> Result<ft_sdk::SessionData, diesel::result::Error> {
78 use diesel::prelude::*;
79 use ft_sdk::schema::fastn_session;
80
81 let data = fastn_session::table
82 .select(fastn_session::data)
83 .filter(fastn_session::id.eq(self.0.as_str()))
84 .first::<String>(conn)?;
85
86 let data: std::collections::HashMap<String, serde_json::Value> =
87 serde_json::from_str(&data).expect("session data must be serializable json object");
88
89 Ok(SessionData::new(self.0.as_str(), data))
90 }
91
92 pub fn set_key<S: AsRef<str>, V: serde::Serialize>(
96 &self,
97 conn: &mut ft_sdk::Connection,
98 k: S,
99 v: V,
100 ) -> Result<SessionID, SetKeyError> {
101 use diesel::prelude::*;
102 use diesel::sql_types::Text;
103 use ft_sdk::schema::fastn_session;
104
105 let value = serde_json::to_string(&v).map_err(SetKeyError::SerdeError)?;
106
107 let sql_set = diesel::dsl::sql::<Text>("json_set(data, ")
109 .bind::<Text, _>(format!("$.{}", k.as_ref()))
110 .sql(", json(")
111 .bind::<Text, _>(value)
112 .sql("))");
113
114 diesel::update(fastn_session::table.filter(fastn_session::id.eq(self.0.as_str())))
115 .set(fastn_session::data.eq(sql_set))
116 .execute(conn)
117 .map_err(SetKeyError::DatabaseError)?;
118
119 Ok(self.clone())
120 }
121}
122
123#[derive(thiserror::Error, Debug)]
124pub enum GetKeyError {
125 #[error("key `{0}` not found in session data")]
126 KeyNotFound(String),
127 #[error("failed to query db: {0:?}")]
128 DatabaseError(diesel::result::Error),
129 #[error("failed to deserialize session data: {0:?}")]
130 SerdeError(serde_json::Error),
131}
132
133#[derive(thiserror::Error, Debug)]
134pub enum SetKeyError {
135 #[error("db error: {0:?}")]
136 DatabaseError(diesel::result::Error),
137 #[error("failed to serialize value: {0:?}")]
138 SerdeError(serde_json::Error),
139}