From 35e81405e28faf15b0c79b5e62e35d2f8062f8d5 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Thu, 18 Jul 2024 10:16:30 +0200 Subject: [PATCH] graphql: allow filtering of sessions by last activity --- crates/handlers/src/graphql/mod.rs | 13 ++++- crates/handlers/src/graphql/model/users.rs | 68 +++++++++++++++++++++- frontend/schema.graphql | 30 ++++++++++ frontend/src/gql/graphql.ts | 12 ++++ frontend/src/gql/schema.ts | 28 +++++++++ 5 files changed, 148 insertions(+), 3 deletions(-) diff --git a/crates/handlers/src/graphql/mod.rs b/crates/handlers/src/graphql/mod.rs index 12dbfebe7..0768af06f 100644 --- a/crates/handlers/src/graphql/mod.rs +++ b/crates/handlers/src/graphql/mod.rs @@ -19,7 +19,7 @@ use std::sync::Arc; use async_graphql::{ extensions::Tracing, http::{playground_source, GraphQLPlaygroundConfig, MultipartOptions}, - EmptySubscription, + EmptySubscription, InputObject, }; use axum::{ async_trait, @@ -30,6 +30,7 @@ use axum::{ Json, }; use axum_extra::typed_header::TypedHeader; +use chrono::{DateTime, Utc}; use futures_util::TryStreamExt; use headers::{authorization::Bearer, Authorization, ContentType, HeaderValue}; use hyper::header::CACHE_CONTROL; @@ -501,3 +502,13 @@ where session.map(Into::into).unwrap_or_default() } } + +/// A filter for dates, with a lower bound and an upper bound +#[derive(InputObject, Default, Clone, Copy)] +pub struct DateFilter { + /// The lower bound of the date range + after: Option>, + + /// The upper bound of the date range + before: Option>, +} diff --git a/crates/handlers/src/graphql/model/users.rs b/crates/handlers/src/graphql/model/users.rs index c60ae7f39..70585d7d9 100644 --- a/crates/handlers/src/graphql/model/users.rs +++ b/crates/handlers/src/graphql/model/users.rs @@ -1,4 +1,4 @@ -// Copyright 2022 The Matrix.org Foundation C.I.C. +// Copyright 2022-2024 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -34,7 +34,7 @@ use super::{ BrowserSession, CompatSession, Cursor, NodeCursor, NodeType, OAuth2Session, PreloadedTotalCount, SessionState, UpstreamOAuth2Link, }; -use crate::graphql::state::ContextExt; +use crate::graphql::{state::ContextExt, DateFilter}; #[derive(Description)] /// A user is an individual's account. @@ -171,6 +171,12 @@ impl User { #[graphql(name = "type", desc = "List only sessions with the given type.")] type_param: Option, + #[graphql( + name = "lastActive", + desc = "List only sessions with a last active time is between the given bounds." + )] + last_active: Option, + #[graphql(desc = "Returns the elements in the list that come after the cursor.")] after: Option, #[graphql(desc = "Returns the elements in the list that come before the cursor.")] @@ -180,6 +186,7 @@ impl User { ) -> Result, async_graphql::Error> { let state = ctx.state(); let mut repo = state.repository().await?; + let last_active = last_active.unwrap_or_default(); query( after, @@ -208,6 +215,15 @@ impl User { None => filter, }; + let filter = match last_active.after { + Some(after) => filter.with_last_active_after(after), + None => filter, + }; + let filter = match last_active.before { + Some(before) => filter.with_last_active_before(before), + None => filter, + }; + let page = repo.compat_session().list(filter, pagination).await?; // Preload the total count if requested @@ -247,6 +263,12 @@ impl User { #[graphql(name = "state", desc = "List only sessions in the given state.")] state_param: Option, + #[graphql( + name = "lastActive", + desc = "List only sessions with a last active time is between the given bounds." + )] + last_active: Option, + #[graphql(desc = "Returns the elements in the list that come after the cursor.")] after: Option, #[graphql(desc = "Returns the elements in the list that come before the cursor.")] @@ -256,6 +278,7 @@ impl User { ) -> Result, async_graphql::Error> { let state = ctx.state(); let mut repo = state.repository().await?; + let last_active = last_active.unwrap_or_default(); query( after, @@ -278,6 +301,15 @@ impl User { None => filter, }; + let filter = match last_active.after { + Some(after) => filter.with_last_active_after(after), + None => filter, + }; + let filter = match last_active.before { + Some(before) => filter.with_last_active_before(before), + None => filter, + }; + let page = repo.browser_session().list(filter, pagination).await?; // Preload the total count if requested @@ -387,6 +419,12 @@ impl User { #[graphql(desc = "List only sessions for the given client.")] client: Option, + #[graphql( + name = "lastActive", + desc = "List only sessions with a last active time is between the given bounds." + )] + last_active: Option, + #[graphql(desc = "Returns the elements in the list that come after the cursor.")] after: Option, #[graphql(desc = "Returns the elements in the list that come before the cursor.")] @@ -396,6 +434,7 @@ impl User { ) -> Result, async_graphql::Error> { let state = ctx.state(); let mut repo = state.repository().await?; + let last_active = last_active.unwrap_or_default(); query( after, @@ -438,6 +477,15 @@ impl User { None => filter, }; + let filter = match last_active.after { + Some(after) => filter.with_last_active_after(after), + None => filter, + }; + let filter = match last_active.before { + Some(before) => filter.with_last_active_before(before), + None => filter, + }; + let page = repo.oauth2_session().list(filter, pagination).await?; let count = if ctx.look_ahead().field("totalCount").exists() { @@ -547,6 +595,12 @@ impl User { #[graphql(name = "device", desc = "List only sessions for the given device.")] device_param: Option, + #[graphql( + name = "lastActive", + desc = "List only sessions with a last active time is between the given bounds." + )] + last_active: Option, + #[graphql( name = "browserSession", desc = "List only sessions for the given session." @@ -563,6 +617,7 @@ impl User { let state = ctx.state(); let requester = ctx.requester(); let mut repo = state.repository().await?; + let last_active = last_active.unwrap_or_default(); query( after, @@ -629,6 +684,15 @@ impl User { None => filter, }; + let filter = match last_active.after { + Some(after) => filter.with_last_active_after(after), + None => filter, + }; + let filter = match last_active.before { + Some(before) => filter.with_last_active_before(before), + None => filter, + }; + let page = repo.app_session().list(filter, pagination).await?; let count = if ctx.look_ahead().field("totalCount").exists() { diff --git a/frontend/schema.graphql b/frontend/schema.graphql index 6e007a8d2..61a87e8c4 100644 --- a/frontend/schema.graphql +++ b/frontend/schema.graphql @@ -509,6 +509,20 @@ interface CreationEvent { createdAt: DateTime! } +""" +A filter for dates, with a lower bound and an upper bound +""" +input DateFilter { + """ + The lower bound of the date range + """ + after: DateTime + """ + The upper bound of the date range + """ + before: DateTime +} + """ Implement the DateTime scalar @@ -1624,6 +1638,10 @@ type User implements Node { """ type: CompatSessionType """ + List only sessions with a last active time is between the given bounds. + """ + lastActive: DateFilter + """ Returns the elements in the list that come after the cursor. """ after: String @@ -1649,6 +1667,10 @@ type User implements Node { """ state: SessionState """ + List only sessions with a last active time is between the given bounds. + """ + lastActive: DateFilter + """ Returns the elements in the list that come after the cursor. """ after: String @@ -1703,6 +1725,10 @@ type User implements Node { """ client: ID """ + List only sessions with a last active time is between the given bounds. + """ + lastActive: DateFilter + """ Returns the elements in the list that come after the cursor. """ after: String @@ -1754,6 +1780,10 @@ type User implements Node { """ device: String """ + List only sessions with a last active time is between the given bounds. + """ + lastActive: DateFilter + """ List only sessions for the given session. """ browserSession: ID diff --git a/frontend/src/gql/graphql.ts b/frontend/src/gql/graphql.ts index 3dde09493..3077d745e 100644 --- a/frontend/src/gql/graphql.ts +++ b/frontend/src/gql/graphql.ts @@ -342,6 +342,14 @@ export type CreationEvent = { createdAt: Scalars['DateTime']['output']; }; +/** A filter for dates, with a lower bound and an upper bound */ +export type DateFilter = { + /** The lower bound of the date range */ + after?: InputMaybe; + /** The upper bound of the date range */ + before?: InputMaybe; +}; + /** The type of a user agent */ export enum DeviceType { /** A mobile phone. Can also sometimes be a tablet. */ @@ -1186,6 +1194,7 @@ export type UserAppSessionsArgs = { device?: InputMaybe; first?: InputMaybe; last?: InputMaybe; + lastActive?: InputMaybe; state?: InputMaybe; }; @@ -1196,6 +1205,7 @@ export type UserBrowserSessionsArgs = { before?: InputMaybe; first?: InputMaybe; last?: InputMaybe; + lastActive?: InputMaybe; state?: InputMaybe; }; @@ -1206,6 +1216,7 @@ export type UserCompatSessionsArgs = { before?: InputMaybe; first?: InputMaybe; last?: InputMaybe; + lastActive?: InputMaybe; state?: InputMaybe; type?: InputMaybe; }; @@ -1237,6 +1248,7 @@ export type UserOauth2SessionsArgs = { client?: InputMaybe; first?: InputMaybe; last?: InputMaybe; + lastActive?: InputMaybe; state?: InputMaybe; }; diff --git a/frontend/src/gql/schema.ts b/frontend/src/gql/schema.ts index 1971ae5cb..622ce21ea 100644 --- a/frontend/src/gql/schema.ts +++ b/frontend/src/gql/schema.ts @@ -3062,6 +3062,13 @@ export default { "name": "Any" } }, + { + "name": "lastActive", + "type": { + "kind": "SCALAR", + "name": "Any" + } + }, { "name": "state", "type": { @@ -3110,6 +3117,13 @@ export default { "name": "Any" } }, + { + "name": "lastActive", + "type": { + "kind": "SCALAR", + "name": "Any" + } + }, { "name": "state", "type": { @@ -3169,6 +3183,13 @@ export default { "name": "Any" } }, + { + "name": "lastActive", + "type": { + "kind": "SCALAR", + "name": "Any" + } + }, { "name": "state", "type": { @@ -3362,6 +3383,13 @@ export default { "name": "Any" } }, + { + "name": "lastActive", + "type": { + "kind": "SCALAR", + "name": "Any" + } + }, { "name": "state", "type": {