graphql: allow filtering of sessions by last activity

This commit is contained in:
Quentin Gliech
2024-07-18 10:16:30 +02:00
parent 57b3aad1b1
commit 35e81405e2
5 changed files with 148 additions and 3 deletions

View File

@@ -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<DateTime<Utc>>,
/// The upper bound of the date range
before: Option<DateTime<Utc>>,
}

View File

@@ -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<CompatSessionType>,
#[graphql(
name = "lastActive",
desc = "List only sessions with a last active time is between the given bounds."
)]
last_active: Option<DateFilter>,
#[graphql(desc = "Returns the elements in the list that come after the cursor.")]
after: Option<String>,
#[graphql(desc = "Returns the elements in the list that come before the cursor.")]
@@ -180,6 +186,7 @@ impl User {
) -> Result<Connection<Cursor, CompatSession, PreloadedTotalCount>, 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<SessionState>,
#[graphql(
name = "lastActive",
desc = "List only sessions with a last active time is between the given bounds."
)]
last_active: Option<DateFilter>,
#[graphql(desc = "Returns the elements in the list that come after the cursor.")]
after: Option<String>,
#[graphql(desc = "Returns the elements in the list that come before the cursor.")]
@@ -256,6 +278,7 @@ impl User {
) -> Result<Connection<Cursor, BrowserSession, PreloadedTotalCount>, 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<ID>,
#[graphql(
name = "lastActive",
desc = "List only sessions with a last active time is between the given bounds."
)]
last_active: Option<DateFilter>,
#[graphql(desc = "Returns the elements in the list that come after the cursor.")]
after: Option<String>,
#[graphql(desc = "Returns the elements in the list that come before the cursor.")]
@@ -396,6 +434,7 @@ impl User {
) -> Result<Connection<Cursor, OAuth2Session, PreloadedTotalCount>, 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<String>,
#[graphql(
name = "lastActive",
desc = "List only sessions with a last active time is between the given bounds."
)]
last_active: Option<DateFilter>,
#[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() {

View File

@@ -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<Utc> 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

View File

@@ -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<Scalars['DateTime']['input']>;
/** The upper bound of the date range */
before?: InputMaybe<Scalars['DateTime']['input']>;
};
/** 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<Scalars['String']['input']>;
first?: InputMaybe<Scalars['Int']['input']>;
last?: InputMaybe<Scalars['Int']['input']>;
lastActive?: InputMaybe<DateFilter>;
state?: InputMaybe<SessionState>;
};
@@ -1196,6 +1205,7 @@ export type UserBrowserSessionsArgs = {
before?: InputMaybe<Scalars['String']['input']>;
first?: InputMaybe<Scalars['Int']['input']>;
last?: InputMaybe<Scalars['Int']['input']>;
lastActive?: InputMaybe<DateFilter>;
state?: InputMaybe<SessionState>;
};
@@ -1206,6 +1216,7 @@ export type UserCompatSessionsArgs = {
before?: InputMaybe<Scalars['String']['input']>;
first?: InputMaybe<Scalars['Int']['input']>;
last?: InputMaybe<Scalars['Int']['input']>;
lastActive?: InputMaybe<DateFilter>;
state?: InputMaybe<SessionState>;
type?: InputMaybe<CompatSessionType>;
};
@@ -1237,6 +1248,7 @@ export type UserOauth2SessionsArgs = {
client?: InputMaybe<Scalars['ID']['input']>;
first?: InputMaybe<Scalars['Int']['input']>;
last?: InputMaybe<Scalars['Int']['input']>;
lastActive?: InputMaybe<DateFilter>;
state?: InputMaybe<SessionState>;
};

View File

@@ -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": {