1use std::net::IpAddr;
8
9use async_trait::async_trait;
10use chrono::{DateTime, Utc};
11use mas_data_model::{BrowserSession, Clock, CompatSession, CompatSsoLogin, Device, User};
12use rand_core::RngCore;
13use ulid::Ulid;
14
15use crate::{Page, Pagination, repository_impl, user::BrowserSessionFilter};
16
17#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18pub enum CompatSessionState {
19    Active,
20    Finished,
21}
22
23impl CompatSessionState {
24    #[must_use]
26    pub fn is_active(self) -> bool {
27        matches!(self, Self::Active)
28    }
29
30    #[must_use]
32    pub fn is_finished(self) -> bool {
33        matches!(self, Self::Finished)
34    }
35}
36
37#[derive(Clone, Copy, Debug, PartialEq, Eq)]
38pub enum CompatSessionType {
39    SsoLogin,
40    Unknown,
41}
42
43impl CompatSessionType {
44    #[must_use]
46    pub fn is_sso_login(self) -> bool {
47        matches!(self, Self::SsoLogin)
48    }
49
50    #[must_use]
52    pub fn is_unknown(self) -> bool {
53        matches!(self, Self::Unknown)
54    }
55}
56
57#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
59pub struct CompatSessionFilter<'a> {
60    user: Option<&'a User>,
61    browser_session: Option<&'a BrowserSession>,
62    browser_session_filter: Option<BrowserSessionFilter<'a>>,
63    state: Option<CompatSessionState>,
64    auth_type: Option<CompatSessionType>,
65    device: Option<&'a Device>,
66    last_active_before: Option<DateTime<Utc>>,
67    last_active_after: Option<DateTime<Utc>>,
68}
69
70impl<'a> CompatSessionFilter<'a> {
71    #[must_use]
73    pub fn new() -> Self {
74        Self::default()
75    }
76
77    #[must_use]
79    pub fn for_user(mut self, user: &'a User) -> Self {
80        self.user = Some(user);
81        self
82    }
83
84    #[must_use]
86    pub fn user(&self) -> Option<&'a User> {
87        self.user
88    }
89
90    #[must_use]
92    pub fn for_device(mut self, device: &'a Device) -> Self {
93        self.device = Some(device);
94        self
95    }
96
97    #[must_use]
99    pub fn device(&self) -> Option<&'a Device> {
100        self.device
101    }
102
103    #[must_use]
105    pub fn for_browser_session(mut self, browser_session: &'a BrowserSession) -> Self {
106        self.browser_session = Some(browser_session);
107        self
108    }
109
110    #[must_use]
112    pub fn for_browser_sessions(
113        mut self,
114        browser_session_filter: BrowserSessionFilter<'a>,
115    ) -> Self {
116        self.browser_session_filter = Some(browser_session_filter);
117        self
118    }
119
120    #[must_use]
122    pub fn browser_session(&self) -> Option<&'a BrowserSession> {
123        self.browser_session
124    }
125
126    #[must_use]
128    pub fn browser_session_filter(&self) -> Option<BrowserSessionFilter<'a>> {
129        self.browser_session_filter
130    }
131
132    #[must_use]
134    pub fn with_last_active_before(mut self, last_active_before: DateTime<Utc>) -> Self {
135        self.last_active_before = Some(last_active_before);
136        self
137    }
138
139    #[must_use]
141    pub fn with_last_active_after(mut self, last_active_after: DateTime<Utc>) -> Self {
142        self.last_active_after = Some(last_active_after);
143        self
144    }
145
146    #[must_use]
150    pub fn last_active_before(&self) -> Option<DateTime<Utc>> {
151        self.last_active_before
152    }
153
154    #[must_use]
158    pub fn last_active_after(&self) -> Option<DateTime<Utc>> {
159        self.last_active_after
160    }
161
162    #[must_use]
164    pub fn active_only(mut self) -> Self {
165        self.state = Some(CompatSessionState::Active);
166        self
167    }
168
169    #[must_use]
171    pub fn finished_only(mut self) -> Self {
172        self.state = Some(CompatSessionState::Finished);
173        self
174    }
175
176    #[must_use]
178    pub fn state(&self) -> Option<CompatSessionState> {
179        self.state
180    }
181
182    #[must_use]
184    pub fn sso_login_only(mut self) -> Self {
185        self.auth_type = Some(CompatSessionType::SsoLogin);
186        self
187    }
188
189    #[must_use]
191    pub fn unknown_only(mut self) -> Self {
192        self.auth_type = Some(CompatSessionType::Unknown);
193        self
194    }
195
196    #[must_use]
198    pub fn auth_type(&self) -> Option<CompatSessionType> {
199        self.auth_type
200    }
201}
202
203#[async_trait]
206pub trait CompatSessionRepository: Send + Sync {
207    type Error;
209
210    async fn lookup(&mut self, id: Ulid) -> Result<Option<CompatSession>, Self::Error>;
222
223    #[expect(clippy::too_many_arguments)]
242    async fn add(
243        &mut self,
244        rng: &mut (dyn RngCore + Send),
245        clock: &dyn Clock,
246        user: &User,
247        device: Device,
248        browser_session: Option<&BrowserSession>,
249        is_synapse_admin: bool,
250        human_name: Option<String>,
251    ) -> Result<CompatSession, Self::Error>;
252
253    async fn finish(
266        &mut self,
267        clock: &dyn Clock,
268        compat_session: CompatSession,
269    ) -> Result<CompatSession, Self::Error>;
270
271    async fn finish_bulk(
284        &mut self,
285        clock: &dyn Clock,
286        filter: CompatSessionFilter<'_>,
287    ) -> Result<usize, Self::Error>;
288
289    async fn list(
302        &mut self,
303        filter: CompatSessionFilter<'_>,
304        pagination: Pagination,
305    ) -> Result<Page<(CompatSession, Option<CompatSsoLogin>)>, Self::Error>;
306
307    async fn count(&mut self, filter: CompatSessionFilter<'_>) -> Result<usize, Self::Error>;
317
318    async fn record_batch_activity(
329        &mut self,
330        activity: Vec<(Ulid, DateTime<Utc>, Option<IpAddr>)>,
331    ) -> Result<(), Self::Error>;
332
333    async fn record_user_agent(
344        &mut self,
345        compat_session: CompatSession,
346        user_agent: String,
347    ) -> Result<CompatSession, Self::Error>;
348
349    async fn set_human_name(
360        &mut self,
361        compat_session: CompatSession,
362        human_name: Option<String>,
363    ) -> Result<CompatSession, Self::Error>;
364}
365
366repository_impl!(CompatSessionRepository:
367    async fn lookup(&mut self, id: Ulid) -> Result<Option<CompatSession>, Self::Error>;
368
369    async fn add(
370        &mut self,
371        rng: &mut (dyn RngCore + Send),
372        clock: &dyn Clock,
373        user: &User,
374        device: Device,
375        browser_session: Option<&BrowserSession>,
376        is_synapse_admin: bool,
377        human_name: Option<String>,
378    ) -> Result<CompatSession, Self::Error>;
379
380    async fn finish(
381        &mut self,
382        clock: &dyn Clock,
383        compat_session: CompatSession,
384    ) -> Result<CompatSession, Self::Error>;
385
386    async fn finish_bulk(
387        &mut self,
388        clock: &dyn Clock,
389        filter: CompatSessionFilter<'_>,
390    ) -> Result<usize, Self::Error>;
391
392    async fn list(
393        &mut self,
394        filter: CompatSessionFilter<'_>,
395        pagination: Pagination,
396    ) -> Result<Page<(CompatSession, Option<CompatSsoLogin>)>, Self::Error>;
397
398    async fn count(&mut self, filter: CompatSessionFilter<'_>) -> Result<usize, Self::Error>;
399
400    async fn record_batch_activity(
401        &mut self,
402        activity: Vec<(Ulid, DateTime<Utc>, Option<IpAddr>)>,
403    ) -> Result<(), Self::Error>;
404
405    async fn record_user_agent(
406        &mut self,
407        compat_session: CompatSession,
408        user_agent: String,
409    ) -> Result<CompatSession, Self::Error>;
410
411    async fn set_human_name(
412        &mut self,
413        compat_session: CompatSession,
414        human_name: Option<String>,
415    ) -> Result<CompatSession, Self::Error>;
416);