mas_handlers/graphql/model/
browser_sessions.rs1use async_graphql::{
8 Context, Description, ID, Object,
9 connection::{Connection, Edge, OpaqueCursor, query},
10};
11use chrono::{DateTime, Utc};
12use mas_data_model::Device;
13use mas_storage::{
14 Pagination, RepositoryAccess, app_session::AppSessionFilter, user::BrowserSessionRepository,
15};
16
17use super::{
18 AppSession, CompatSession, Cursor, NodeCursor, NodeType, OAuth2Session, PreloadedTotalCount,
19 SessionState, User, UserAgent,
20};
21use crate::graphql::state::ContextExt;
22
23#[derive(Description)]
25pub struct BrowserSession(pub mas_data_model::BrowserSession);
26
27impl From<mas_data_model::BrowserSession> for BrowserSession {
28 fn from(v: mas_data_model::BrowserSession) -> Self {
29 Self(v)
30 }
31}
32
33#[Object(use_type_description)]
34impl BrowserSession {
35 pub async fn id(&self) -> ID {
37 NodeType::BrowserSession.id(self.0.id)
38 }
39
40 async fn user(&self) -> User {
42 User(self.0.user.clone())
43 }
44
45 async fn last_authentication(
47 &self,
48 ctx: &Context<'_>,
49 ) -> Result<Option<Authentication>, async_graphql::Error> {
50 let state = ctx.state();
51 let mut repo = state.repository().await?;
52
53 let last_authentication = repo
54 .browser_session()
55 .get_last_authentication(&self.0)
56 .await?;
57
58 repo.cancel().await?;
59
60 Ok(last_authentication.map(Authentication))
61 }
62
63 pub async fn created_at(&self) -> DateTime<Utc> {
65 self.0.created_at
66 }
67
68 pub async fn finished_at(&self) -> Option<DateTime<Utc>> {
70 self.0.finished_at
71 }
72
73 pub async fn state(&self) -> SessionState {
75 if self.0.finished_at.is_some() {
76 SessionState::Finished
77 } else {
78 SessionState::Active
79 }
80 }
81
82 pub async fn user_agent(&self) -> Option<UserAgent> {
84 self.0.user_agent.clone().map(UserAgent::from)
85 }
86
87 pub async fn last_active_ip(&self) -> Option<String> {
89 self.0.last_active_ip.map(|ip| ip.to_string())
90 }
91
92 pub async fn last_active_at(&self) -> Option<DateTime<Utc>> {
94 self.0.last_active_at
95 }
96
97 #[allow(clippy::too_many_arguments)]
100 async fn app_sessions(
101 &self,
102 ctx: &Context<'_>,
103
104 #[graphql(name = "state", desc = "List only sessions in the given state.")]
105 state_param: Option<SessionState>,
106
107 #[graphql(name = "device", desc = "List only sessions for the given device.")]
108 device_param: Option<String>,
109
110 #[graphql(desc = "Returns the elements in the list that come after the cursor.")]
111 after: Option<String>,
112 #[graphql(desc = "Returns the elements in the list that come before the cursor.")]
113 before: Option<String>,
114 #[graphql(desc = "Returns the first *n* elements from the list.")] first: Option<i32>,
115 #[graphql(desc = "Returns the last *n* elements from the list.")] last: Option<i32>,
116 ) -> Result<Connection<Cursor, AppSession, PreloadedTotalCount>, async_graphql::Error> {
117 let state = ctx.state();
118 let mut repo = state.repository().await?;
119
120 query(
121 after,
122 before,
123 first,
124 last,
125 async |after, before, first, last| {
126 let after_id = after
127 .map(|x: OpaqueCursor<NodeCursor>| {
128 x.extract_for_types(&[NodeType::OAuth2Session, NodeType::CompatSession])
129 })
130 .transpose()?;
131 let before_id = before
132 .map(|x: OpaqueCursor<NodeCursor>| {
133 x.extract_for_types(&[NodeType::OAuth2Session, NodeType::CompatSession])
134 })
135 .transpose()?;
136 let pagination = Pagination::try_new(before_id, after_id, first, last)?;
137
138 let device_param = device_param.map(Device::try_from).transpose()?;
139
140 let filter = AppSessionFilter::new().for_browser_session(&self.0);
141
142 let filter = match state_param {
143 Some(SessionState::Active) => filter.active_only(),
144 Some(SessionState::Finished) => filter.finished_only(),
145 None => filter,
146 };
147
148 let filter = match device_param.as_ref() {
149 Some(device) => filter.for_device(device),
150 None => filter,
151 };
152
153 let page = repo.app_session().list(filter, pagination).await?;
154
155 let count = if ctx.look_ahead().field("totalCount").exists() {
156 Some(repo.app_session().count(filter).await?)
157 } else {
158 None
159 };
160
161 repo.cancel().await?;
162
163 let mut connection = Connection::with_additional_fields(
164 page.has_previous_page,
165 page.has_next_page,
166 PreloadedTotalCount(count),
167 );
168
169 connection
170 .edges
171 .extend(page.edges.into_iter().map(|s| match s {
172 mas_storage::app_session::AppSession::Compat(session) => Edge::new(
173 OpaqueCursor(NodeCursor(NodeType::CompatSession, session.id)),
174 AppSession::CompatSession(Box::new(CompatSession::new(*session))),
175 ),
176 mas_storage::app_session::AppSession::OAuth2(session) => Edge::new(
177 OpaqueCursor(NodeCursor(NodeType::OAuth2Session, session.id)),
178 AppSession::OAuth2Session(Box::new(OAuth2Session(*session))),
179 ),
180 }));
181
182 Ok::<_, async_graphql::Error>(connection)
183 },
184 )
185 .await
186 }
187}
188
189#[derive(Description)]
192pub struct Authentication(pub mas_data_model::Authentication);
193
194#[Object(use_type_description)]
195impl Authentication {
196 pub async fn id(&self) -> ID {
198 NodeType::Authentication.id(self.0.id)
199 }
200
201 pub async fn created_at(&self) -> DateTime<Utc> {
203 self.0.created_at
204 }
205}