From 2064c11d9b80ed3c01cd13fe94c7f4b359030948 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Mon, 14 Nov 2022 17:44:03 +0100 Subject: [PATCH] Simple list of compat sessions --- Cargo.lock | 2 + crates/graphql/Cargo.toml | 2 + crates/graphql/src/lib.rs | 33 ++- crates/graphql/src/model/browser_sessions.rs | 6 +- crates/graphql/src/model/compat_sessions.rs | 6 +- crates/graphql/src/model/cursor.rs | 9 +- crates/graphql/src/model/mod.rs | 20 +- crates/graphql/src/model/node.rs | 107 +++++++ crates/graphql/src/model/oauth.rs | 6 +- crates/graphql/src/model/users.rs | 8 +- frontend/.eslintrc.cjs | 6 +- frontend/schema.graphql | 4 + frontend/src/components/CompatSsoLogin.tsx | 43 ++- .../src/components/CompatSsoLoginList.tsx | 21 +- .../CompatSsoLoginListQuery.graphql.ts | 265 ++++++++++++++++++ .../CompatSsoLoginList_user.graphql.ts | 118 ++++++-- .../CompatSsoLogin_login.graphql.ts | 65 ++++- frontend/src/pages/Home.tsx | 4 +- .../pages/__generated__/HomeQuery.graphql.ts | 144 ++++++++-- 19 files changed, 771 insertions(+), 98 deletions(-) create mode 100644 crates/graphql/src/model/node.rs create mode 100644 frontend/src/components/__generated__/CompatSsoLoginListQuery.graphql.ts diff --git a/Cargo.lock b/Cargo.lock index f79d734c..b5cabad9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2605,7 +2605,9 @@ dependencies = [ "oauth2-types", "serde", "sqlx", + "thiserror", "tokio", + "tracing", "ulid", "url", ] diff --git a/crates/graphql/Cargo.toml b/crates/graphql/Cargo.toml index 011d0b23..420fb43a 100644 --- a/crates/graphql/Cargo.toml +++ b/crates/graphql/Cargo.toml @@ -10,7 +10,9 @@ async-graphql = { version = "4.0.16", features = ["chrono", "url"] } chrono = "0.4.23" serde = { version = "1.0.147", features = ["derive"] } sqlx = { version = "0.6.2", features = ["runtime-tokio-rustls", "postgres"] } +thiserror = "1.0.37" tokio = { version = "1.21.2", features = ["time"] } +tracing = "0.1.37" ulid = "1.0.0" url = "2.3.1" diff --git a/crates/graphql/src/lib.rs b/crates/graphql/src/lib.rs index bf4b623d..fa73924a 100644 --- a/crates/graphql/src/lib.rs +++ b/crates/graphql/src/lib.rs @@ -22,8 +22,9 @@ #![warn(clippy::pedantic)] #![allow(clippy::module_name_repetitions, clippy::missing_errors_doc)] -use async_graphql::{Context, Description, EmptyMutation, EmptySubscription}; +use async_graphql::{Context, Description, EmptyMutation, EmptySubscription, ID}; use mas_axum_utils::SessionInfo; +use model::NodeType; use sqlx::PgPool; use self::model::{BrowserSession, Node, User}; @@ -78,4 +79,34 @@ impl RootQuery { Ok(session.map(User::from)) } + + /// Fetches an object given its ID. + async fn node(&self, ctx: &Context<'_>, id: ID) -> Result, async_graphql::Error> { + let (node_type, id) = NodeType::from_id(&id)?; + let database = ctx.data::()?; + let session_info = ctx.data::()?; + let mut conn = database.acquire().await?; + let session = session_info.load_session(&mut conn).await?; + + let Some(session) = session else { return Ok(None) }; + + match node_type { + // TODO + NodeType::Authentication + | NodeType::BrowserSession + | NodeType::CompatSession + | NodeType::CompatSsoLogin + | NodeType::OAuth2Client + | NodeType::UserEmail + | NodeType::OAuth2Session => Ok(None), + + NodeType::User => { + if session.user.data == id { + Ok(Some(Box::new(User(session.user)).into())) + } else { + Ok(None) + } + } + } + } } diff --git a/crates/graphql/src/model/browser_sessions.rs b/crates/graphql/src/model/browser_sessions.rs index 1ac79513..a9d97ce1 100644 --- a/crates/graphql/src/model/browser_sessions.rs +++ b/crates/graphql/src/model/browser_sessions.rs @@ -16,7 +16,7 @@ use async_graphql::{Description, Object, ID}; use chrono::{DateTime, Utc}; use mas_storage::PostgresqlBackend; -use super::User; +use super::{NodeType, User}; /// A browser session represents a logged in user in a browser. #[derive(Description)] @@ -32,7 +32,7 @@ impl From> for BrowserSession impl BrowserSession { /// ID of the object. pub async fn id(&self) -> ID { - ID(self.0.data.to_string()) + NodeType::BrowserSession.id(self.0.data) } /// The user logged in this session. @@ -60,7 +60,7 @@ pub struct Authentication(pub mas_data_model::Authentication) impl Authentication { /// ID of the object. pub async fn id(&self) -> ID { - ID(self.0.data.to_string()) + NodeType::Authentication.id(self.0.data) } /// When the object was created. diff --git a/crates/graphql/src/model/compat_sessions.rs b/crates/graphql/src/model/compat_sessions.rs index 70789493..d4461434 100644 --- a/crates/graphql/src/model/compat_sessions.rs +++ b/crates/graphql/src/model/compat_sessions.rs @@ -18,7 +18,7 @@ use mas_data_model::CompatSsoLoginState; use mas_storage::PostgresqlBackend; use url::Url; -use super::User; +use super::{NodeType, User}; /// A compat session represents a client session which used the legacy Matrix /// login API. @@ -29,7 +29,7 @@ pub struct CompatSession(pub mas_data_model::CompatSession); impl CompatSession { /// ID of the object. pub async fn id(&self) -> ID { - ID(self.0.data.to_string()) + NodeType::CompatSession.id(self.0.data) } /// The user authorized for this session. @@ -62,7 +62,7 @@ pub struct CompatSsoLogin(pub mas_data_model::CompatSsoLogin) impl CompatSsoLogin { /// ID of the object. pub async fn id(&self) -> ID { - ID(self.0.data.to_string()) + NodeType::CompatSsoLogin.id(self.0.data) } /// When the object was created. diff --git a/crates/graphql/src/model/cursor.rs b/crates/graphql/src/model/cursor.rs index 14d6b58c..3588d08e 100644 --- a/crates/graphql/src/model/cursor.rs +++ b/crates/graphql/src/model/cursor.rs @@ -16,14 +16,7 @@ use async_graphql::connection::OpaqueCursor; use serde::{Deserialize, Serialize}; use ulid::Ulid; -#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, Copy)] -#[serde(rename = "snake_case")] -pub enum NodeType { - UserEmail, - BrowserSession, - CompatSsoLogin, - OAuth2Session, -} +pub use super::NodeType; #[derive(Serialize, Deserialize, PartialEq, Eq)] pub struct NodeCursor(pub NodeType, pub Ulid); diff --git a/crates/graphql/src/model/mod.rs b/crates/graphql/src/model/mod.rs index 1984ee35..f17519aa 100644 --- a/crates/graphql/src/model/mod.rs +++ b/crates/graphql/src/model/mod.rs @@ -12,37 +12,25 @@ // See the License for the specific language governing permissions and // limitations under the License. -use async_graphql::{Interface, ID}; +use async_graphql::Interface; use chrono::{DateTime, Utc}; mod browser_sessions; mod compat_sessions; mod cursor; +mod node; mod oauth; mod users; pub use self::{ browser_sessions::{Authentication, BrowserSession}, compat_sessions::{CompatSession, CompatSsoLogin}, - cursor::{Cursor, NodeCursor, NodeType}, + cursor::{Cursor, NodeCursor}, + node::{Node, NodeType}, oauth::{OAuth2Client, OAuth2Consent, OAuth2Session}, users::{User, UserEmail}, }; -/// An object with an ID. -#[derive(Interface)] -#[graphql(field(name = "id", desc = "ID of the object.", type = "ID"))] -pub enum Node { - Authentication(Box), - BrowserSession(Box), - CompatSession(Box), - CompatSsoLogin(Box), - OAuth2Client(Box), - OAuth2Session(Box), - User(Box), - UserEmail(Box), -} - #[derive(Interface)] #[graphql(field( name = "created_at", diff --git a/crates/graphql/src/model/node.rs b/crates/graphql/src/model/node.rs new file mode 100644 index 00000000..59d24513 --- /dev/null +++ b/crates/graphql/src/model/node.rs @@ -0,0 +1,107 @@ +// Copyright 2022 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. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use async_graphql::{Interface, ID}; +use serde::{Deserialize, Serialize}; +use thiserror::Error; +use ulid::Ulid; + +use super::{ + Authentication, BrowserSession, CompatSession, CompatSsoLogin, OAuth2Client, OAuth2Session, + User, UserEmail, +}; + +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum NodeType { + Authentication, + BrowserSession, + CompatSession, + CompatSsoLogin, + OAuth2Client, + OAuth2Session, + User, + UserEmail, +} + +#[derive(Debug, Error)] +#[error("invalid id")] +pub enum InvalidID { + InvalidFormat, + InvalidUlid(#[from] ulid::DecodeError), + UnknownPrefix, +} + +impl NodeType { + fn to_prefix(self) -> &'static str { + match self { + NodeType::Authentication => "authentication", + NodeType::BrowserSession => "browser_session", + NodeType::CompatSession => "compat_session", + NodeType::CompatSsoLogin => "compat_sso_login", + NodeType::OAuth2Client => "oauth2_client", + NodeType::OAuth2Session => "oauth2_session", + NodeType::User => "user", + NodeType::UserEmail => "user_email", + } + } + + fn from_prefix(prefix: &str) -> Option { + match prefix { + "authentication" => Some(NodeType::Authentication), + "browser_session" => Some(NodeType::BrowserSession), + "compat_session" => Some(NodeType::CompatSession), + "compat_sso_login" => Some(NodeType::CompatSsoLogin), + "oauth2_client" => Some(NodeType::OAuth2Client), + "oauth2_session" => Some(NodeType::OAuth2Session), + "user" => Some(NodeType::User), + "user_email" => Some(NodeType::UserEmail), + _ => None, + } + } + + pub fn serialize(self, id: impl Into) -> String { + let prefix = self.to_prefix(); + let id = id.into(); + format!("{prefix}:{id}") + } + + pub fn id(self, id: impl Into) -> ID { + ID(self.serialize(id)) + } + + pub fn deserialize(serialized: &str) -> Result<(Self, Ulid), InvalidID> { + let (prefix, id) = serialized.split_once(':').ok_or(InvalidID::InvalidFormat)?; + let prefix = NodeType::from_prefix(prefix).ok_or(InvalidID::UnknownPrefix)?; + let id = id.parse()?; + Ok((prefix, id)) + } + + pub fn from_id(id: &ID) -> Result<(Self, Ulid), InvalidID> { + Self::deserialize(&id.0) + } +} + +/// An object with an ID. +#[derive(Interface)] +#[graphql(field(name = "id", desc = "ID of the object.", type = "ID"))] +pub enum Node { + Authentication(Box), + BrowserSession(Box), + CompatSession(Box), + CompatSsoLogin(Box), + OAuth2Client(Box), + OAuth2Session(Box), + User(Box), + UserEmail(Box), +} diff --git a/crates/graphql/src/model/oauth.rs b/crates/graphql/src/model/oauth.rs index f634001d..2e306ee2 100644 --- a/crates/graphql/src/model/oauth.rs +++ b/crates/graphql/src/model/oauth.rs @@ -19,7 +19,7 @@ use sqlx::PgPool; use ulid::Ulid; use url::Url; -use super::{BrowserSession, User}; +use super::{BrowserSession, NodeType, User}; /// An OAuth 2.0 session represents a client session which used the OAuth APIs /// to login. @@ -30,7 +30,7 @@ pub struct OAuth2Session(pub mas_data_model::Session); impl OAuth2Session { /// ID of the object. pub async fn id(&self) -> ID { - ID(self.0.data.to_string()) + NodeType::OAuth2Session.id(self.0.data) } /// OAuth 2.0 client used by this session. @@ -62,7 +62,7 @@ pub struct OAuth2Client(pub mas_data_model::Client); impl OAuth2Client { /// ID of the object. pub async fn id(&self) -> ID { - ID(self.0.data.to_string()) + NodeType::OAuth2Client.id(self.0.data) } /// OAuth 2.0 client ID diff --git a/crates/graphql/src/model/users.rs b/crates/graphql/src/model/users.rs index 9b302208..b9aa5283 100644 --- a/crates/graphql/src/model/users.rs +++ b/crates/graphql/src/model/users.rs @@ -44,7 +44,7 @@ impl From> for User { impl User { /// ID of the object. pub async fn id(&self) -> ID { - ID(self.0.data.to_string()) + NodeType::User.id(self.0.data) } /// Username chosen by the user. @@ -79,10 +79,10 @@ impl User { |after, before, first, last| async move { let mut conn = database.acquire().await?; let after_id = after - .map(|x: OpaqueCursor| x.extract_for_type(NodeType::UserEmail)) + .map(|x: OpaqueCursor| x.extract_for_type(NodeType::CompatSsoLogin)) .transpose()?; let before_id = before - .map(|x: OpaqueCursor| x.extract_for_type(NodeType::UserEmail)) + .map(|x: OpaqueCursor| x.extract_for_type(NodeType::CompatSsoLogin)) .transpose()?; let (has_previous_page, has_next_page, edges) = @@ -262,7 +262,7 @@ pub struct UserEmail(mas_data_model::UserEmail); impl UserEmail { /// ID of the object. pub async fn id(&self) -> ID { - ID(self.0.data.to_string()) + NodeType::UserEmail.id(self.0.data) } /// Email address diff --git a/frontend/.eslintrc.cjs b/frontend/.eslintrc.cjs index b221dafe..f35aa7eb 100644 --- a/frontend/.eslintrc.cjs +++ b/frontend/.eslintrc.cjs @@ -88,10 +88,8 @@ module.exports = { rules: { "@graphql-eslint/known-fragment-names": "off", "@graphql-eslint/no-unused-fragments": "off", - "@graphql-eslint/known-directives": [ - "error", - { ignoreClientDirectives: ["connection", "refetchable"] }, - ], + "@graphql-eslint/unused-arguments": "off", + "@graphql-eslint/known-directives": "off", // This rule is copied from the 'operations-recommended' config, // but without the 'Query' forbidden suffix on operations, // since it directly clashes with the relay operation naming convention diff --git a/frontend/schema.graphql b/frontend/schema.graphql index b6b922d8..ed48a3a1 100644 --- a/frontend/schema.graphql +++ b/frontend/schema.graphql @@ -293,6 +293,10 @@ type RootQuery { Get the current logged in user """ currentUser: User + """ + Fetches an object given its ID. + """ + node(id: ID!): Node } """ diff --git a/frontend/src/components/CompatSsoLogin.tsx b/frontend/src/components/CompatSsoLogin.tsx index 1a66e57f..ae516600 100644 --- a/frontend/src/components/CompatSsoLogin.tsx +++ b/frontend/src/components/CompatSsoLogin.tsx @@ -25,12 +25,53 @@ const CompatSsoLogin: React.FC = ({ login }) => { fragment CompatSsoLogin_login on CompatSsoLogin { id redirectUri + createdAt + session { + id + createdAt + deviceId + finishedAt + } } `, login ); - return <>{data.redirectUri}; + let info = null; + if (data.session) { + info = ( + <> +
+ Started:{" "} + {data.session.createdAt} +
+ {data.session.finishedAt ? ( +
+ Finished:{" "} + {data.session.createdAt} +
+ ) : null} +
+ Device ID:{" "} + + {data.session.deviceId} + +
+ + ); + } + + return ( +
+
+ Requested: {data.createdAt} +
+ {info} +
+ Redirect URI: {data.redirectUri} +
+
+ ); }; export default CompatSsoLogin; diff --git a/frontend/src/components/CompatSsoLoginList.tsx b/frontend/src/components/CompatSsoLoginList.tsx index a9536020..106f7654 100644 --- a/frontend/src/components/CompatSsoLoginList.tsx +++ b/frontend/src/components/CompatSsoLoginList.tsx @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { graphql, useFragment } from "react-relay"; +import { graphql, usePaginationFragment } from "react-relay"; import CompatSsoLogin from "./CompatSsoLogin"; import { CompatSsoLoginList_user$key } from "./__generated__/CompatSsoLoginList_user.graphql"; @@ -21,12 +21,15 @@ type Props = { }; const CompatSsoLoginList: React.FC = ({ user }) => { - const data = useFragment( + const { data, loadNext, hasNext } = usePaginationFragment( graphql` - fragment CompatSsoLoginList_user on User { - compatSsoLogins(first: 10) { + fragment CompatSsoLoginList_user on User + @refetchable(queryName: "CompatSsoLoginListQuery") { + compatSsoLogins(first: $count, after: $cursor) + @connection(key: "CompatSsoLoginList_user_compatSsoLogins") { edges { node { + id ...CompatSsoLogin_login } } @@ -37,10 +40,16 @@ const CompatSsoLoginList: React.FC = ({ user }) => { ); return ( -
+
+

List of compatibility sessions:

{data.compatSsoLogins.edges.map((n) => ( - + ))} + {hasNext ? ( + + ) : null}
); }; diff --git a/frontend/src/components/__generated__/CompatSsoLoginListQuery.graphql.ts b/frontend/src/components/__generated__/CompatSsoLoginListQuery.graphql.ts new file mode 100644 index 00000000..13d8e643 --- /dev/null +++ b/frontend/src/components/__generated__/CompatSsoLoginListQuery.graphql.ts @@ -0,0 +1,265 @@ +/** + * @generated SignedSource<<3e111db0c30af3251a88326deea5562c>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest, Query } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type CompatSsoLoginListQuery$variables = { + count?: number | null; + cursor?: string | null; + id: string; +}; +export type CompatSsoLoginListQuery$data = { + readonly node: { + readonly " $fragmentSpreads": FragmentRefs<"CompatSsoLoginList_user">; + } | null; +}; +export type CompatSsoLoginListQuery = { + response: CompatSsoLoginListQuery$data; + variables: CompatSsoLoginListQuery$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "count" + }, + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "cursor" + }, + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "id" + } +], +v1 = [ + { + "kind": "Variable", + "name": "id", + "variableName": "id" + } +], +v2 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null +}, +v3 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}, +v4 = [ + { + "kind": "Variable", + "name": "after", + "variableName": "cursor" + }, + { + "kind": "Variable", + "name": "first", + "variableName": "count" + } +], +v5 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "createdAt", + "storageKey": null +}; +return { + "fragment": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Fragment", + "metadata": null, + "name": "CompatSsoLoginListQuery", + "selections": [ + { + "alias": null, + "args": (v1/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + { + "args": null, + "kind": "FragmentSpread", + "name": "CompatSsoLoginList_user" + } + ], + "storageKey": null + } + ], + "type": "RootQuery", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "CompatSsoLoginListQuery", + "selections": [ + { + "alias": null, + "args": (v1/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v2/*: any*/), + (v3/*: any*/), + { + "kind": "InlineFragment", + "selections": [ + { + "alias": null, + "args": (v4/*: any*/), + "concreteType": "CompatSsoLoginConnection", + "kind": "LinkedField", + "name": "compatSsoLogins", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "CompatSsoLoginEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "CompatSsoLogin", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v3/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "redirectUri", + "storageKey": null + }, + (v5/*: any*/), + { + "alias": null, + "args": null, + "concreteType": "CompatSession", + "kind": "LinkedField", + "name": "session", + "plural": false, + "selections": [ + (v3/*: any*/), + (v5/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "deviceId", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "finishedAt", + "storageKey": null + } + ], + "storageKey": null + }, + (v2/*: any*/) + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "cursor", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "PageInfo", + "kind": "LinkedField", + "name": "pageInfo", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "endCursor", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "hasNextPage", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": (v4/*: any*/), + "filters": null, + "handle": "connection", + "key": "CompatSsoLoginList_user_compatSsoLogins", + "kind": "LinkedHandle", + "name": "compatSsoLogins" + } + ], + "type": "User", + "abstractKey": null + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "45c6342d3a53a3e3b2b18617796cf654", + "id": null, + "metadata": {}, + "name": "CompatSsoLoginListQuery", + "operationKind": "query", + "text": "query CompatSsoLoginListQuery(\n $count: Int\n $cursor: String\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...CompatSsoLoginList_user\n id\n }\n}\n\nfragment CompatSsoLoginList_user on User {\n compatSsoLogins(first: $count, after: $cursor) {\n edges {\n node {\n id\n ...CompatSsoLogin_login\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n id\n}\n\nfragment CompatSsoLogin_login on CompatSsoLogin {\n id\n redirectUri\n createdAt\n session {\n id\n createdAt\n deviceId\n finishedAt\n }\n}\n" + } +}; +})(); + +(node as any).hash = "cafc795d1bf9643ac6155c017e66c858"; + +export default node; diff --git a/frontend/src/components/__generated__/CompatSsoLoginList_user.graphql.ts b/frontend/src/components/__generated__/CompatSsoLoginList_user.graphql.ts index e272bc66..2f89c607 100644 --- a/frontend/src/components/__generated__/CompatSsoLoginList_user.graphql.ts +++ b/frontend/src/components/__generated__/CompatSsoLoginList_user.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<> + * @generated SignedSource<<4ace8ea8668e3dc638df21400c690bd8>> * @lightSyntaxTransform * @nogrep */ @@ -8,16 +8,18 @@ /* eslint-disable */ // @ts-nocheck -import { Fragment, ReaderFragment } from 'relay-runtime'; +import { ReaderFragment, RefetchableFragment } from 'relay-runtime'; import { FragmentRefs } from "relay-runtime"; export type CompatSsoLoginList_user$data = { readonly compatSsoLogins: { readonly edges: ReadonlyArray<{ readonly node: { + readonly id: string; readonly " $fragmentSpreads": FragmentRefs<"CompatSsoLogin_login">; }; }>; }; + readonly id: string; readonly " $fragmentType": "CompatSsoLoginList_user"; }; export type CompatSsoLoginList_user$key = { @@ -25,24 +27,64 @@ export type CompatSsoLoginList_user$key = { readonly " $fragmentSpreads": FragmentRefs<"CompatSsoLoginList_user">; }; -const node: ReaderFragment = { - "argumentDefinitions": [], +import CompatSsoLoginListQuery_graphql from './CompatSsoLoginListQuery.graphql'; + +const node: ReaderFragment = (function(){ +var v0 = [ + "compatSsoLogins" +], +v1 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}; +return { + "argumentDefinitions": [ + { + "kind": "RootArgument", + "name": "count" + }, + { + "kind": "RootArgument", + "name": "cursor" + } + ], "kind": "Fragment", - "metadata": null, + "metadata": { + "connection": [ + { + "count": "count", + "cursor": "cursor", + "direction": "forward", + "path": (v0/*: any*/) + } + ], + "refetch": { + "connection": { + "forward": { + "count": "count", + "cursor": "cursor" + }, + "backward": null, + "path": (v0/*: any*/) + }, + "fragmentPathInResult": [ + "node" + ], + "operation": CompatSsoLoginListQuery_graphql, + "identifierField": "id" + } + }, "name": "CompatSsoLoginList_user", "selections": [ { - "alias": null, - "args": [ - { - "kind": "Literal", - "name": "first", - "value": 10 - } - ], + "alias": "compatSsoLogins", + "args": null, "concreteType": "CompatSsoLoginConnection", "kind": "LinkedField", - "name": "compatSsoLogins", + "name": "__CompatSsoLoginList_user_compatSsoLogins_connection", "plural": false, "selections": [ { @@ -61,25 +103,67 @@ const node: ReaderFragment = { "name": "node", "plural": false, "selections": [ + (v1/*: any*/), { "args": null, "kind": "FragmentSpread", "name": "CompatSsoLogin_login" + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null } ], "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "cursor", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "PageInfo", + "kind": "LinkedField", + "name": "pageInfo", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "endCursor", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "hasNextPage", + "storageKey": null } ], "storageKey": null } ], - "storageKey": "compatSsoLogins(first:10)" - } + "storageKey": null + }, + (v1/*: any*/) ], "type": "User", "abstractKey": null }; +})(); -(node as any).hash = "b70f4b63784afe8f1f69c78198194cc9"; +(node as any).hash = "cafc795d1bf9643ac6155c017e66c858"; export default node; diff --git a/frontend/src/components/__generated__/CompatSsoLogin_login.graphql.ts b/frontend/src/components/__generated__/CompatSsoLogin_login.graphql.ts index 73a4b7c7..a89d86d8 100644 --- a/frontend/src/components/__generated__/CompatSsoLogin_login.graphql.ts +++ b/frontend/src/components/__generated__/CompatSsoLogin_login.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<> + * @generated SignedSource<<20e2b233e5154ea60632046abc2aa29a>> * @lightSyntaxTransform * @nogrep */ @@ -11,8 +11,15 @@ import { Fragment, ReaderFragment } from 'relay-runtime'; import { FragmentRefs } from "relay-runtime"; export type CompatSsoLogin_login$data = { + readonly createdAt: any; readonly id: string; readonly redirectUri: any; + readonly session: { + readonly createdAt: any; + readonly deviceId: string; + readonly finishedAt: any | null; + readonly id: string; + } | null; readonly " $fragmentType": "CompatSsoLogin_login"; }; export type CompatSsoLogin_login$key = { @@ -20,31 +27,69 @@ export type CompatSsoLogin_login$key = { readonly " $fragmentSpreads": FragmentRefs<"CompatSsoLogin_login">; }; -const node: ReaderFragment = { +const node: ReaderFragment = (function(){ +var v0 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}, +v1 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "createdAt", + "storageKey": null +}; +return { "argumentDefinitions": [], "kind": "Fragment", "metadata": null, "name": "CompatSsoLogin_login", "selections": [ - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "id", - "storageKey": null - }, + (v0/*: any*/), { "alias": null, "args": null, "kind": "ScalarField", "name": "redirectUri", "storageKey": null + }, + (v1/*: any*/), + { + "alias": null, + "args": null, + "concreteType": "CompatSession", + "kind": "LinkedField", + "name": "session", + "plural": false, + "selections": [ + (v0/*: any*/), + (v1/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "deviceId", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "finishedAt", + "storageKey": null + } + ], + "storageKey": null } ], "type": "CompatSsoLogin", "abstractKey": null }; +})(); -(node as any).hash = "e1151a93f1ba4a56332b8aa6129f7bfe"; +(node as any).hash = "7be3b416b1023cea0de7a87b9738e5d5"; export default node; diff --git a/frontend/src/pages/Home.tsx b/frontend/src/pages/Home.tsx index 54fb5694..2d620437 100644 --- a/frontend/src/pages/Home.tsx +++ b/frontend/src/pages/Home.tsx @@ -20,7 +20,7 @@ import type { HomeQuery } from "./__generated__/HomeQuery.graphql"; const Home: React.FC = () => { const data = useLazyLoadQuery( graphql` - query HomeQuery { + query HomeQuery($count: Int!, $cursor: String) { currentUser { id username @@ -29,7 +29,7 @@ const Home: React.FC = () => { } } `, - {} + { count: 2 } ); if (data.currentUser) { diff --git a/frontend/src/pages/__generated__/HomeQuery.graphql.ts b/frontend/src/pages/__generated__/HomeQuery.graphql.ts index 79fe3ac5..b48ff3b2 100644 --- a/frontend/src/pages/__generated__/HomeQuery.graphql.ts +++ b/frontend/src/pages/__generated__/HomeQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<94462aca9c48eae79c22a11003d865c3>> + * @generated SignedSource<<874ab84c1095ab907f12b91b55f4bf2c>> * @lightSyntaxTransform * @nogrep */ @@ -10,7 +10,10 @@ import { ConcreteRequest, Query } from 'relay-runtime'; import { FragmentRefs } from "relay-runtime"; -export type HomeQuery$variables = {}; +export type HomeQuery$variables = { + count: number; + cursor?: string | null; +}; export type HomeQuery$data = { readonly currentUser: { readonly id: string; @@ -24,23 +27,54 @@ export type HomeQuery = { }; const node: ConcreteRequest = (function(){ -var v0 = { +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "count" + }, + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "cursor" + } +], +v1 = { "alias": null, "args": null, "kind": "ScalarField", "name": "id", "storageKey": null }, -v1 = { +v2 = { "alias": null, "args": null, "kind": "ScalarField", "name": "username", "storageKey": null +}, +v3 = [ + { + "kind": "Variable", + "name": "after", + "variableName": "cursor" + }, + { + "kind": "Variable", + "name": "first", + "variableName": "count" + } +], +v4 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "createdAt", + "storageKey": null }; return { "fragment": { - "argumentDefinitions": [], + "argumentDefinitions": (v0/*: any*/), "kind": "Fragment", "metadata": null, "name": "HomeQuery", @@ -53,8 +87,8 @@ return { "name": "currentUser", "plural": false, "selections": [ - (v0/*: any*/), (v1/*: any*/), + (v2/*: any*/), { "args": null, "kind": "FragmentSpread", @@ -69,7 +103,7 @@ return { }, "kind": "Request", "operation": { - "argumentDefinitions": [], + "argumentDefinitions": (v0/*: any*/), "kind": "Operation", "name": "HomeQuery", "selections": [ @@ -81,17 +115,11 @@ return { "name": "currentUser", "plural": false, "selections": [ - (v0/*: any*/), (v1/*: any*/), + (v2/*: any*/), { "alias": null, - "args": [ - { - "kind": "Literal", - "name": "first", - "value": 10 - } - ], + "args": (v3/*: any*/), "concreteType": "CompatSsoLoginConnection", "kind": "LinkedField", "name": "compatSsoLogins", @@ -113,22 +141,98 @@ return { "name": "node", "plural": false, "selections": [ - (v0/*: any*/), + (v1/*: any*/), { "alias": null, "args": null, "kind": "ScalarField", "name": "redirectUri", "storageKey": null + }, + (v4/*: any*/), + { + "alias": null, + "args": null, + "concreteType": "CompatSession", + "kind": "LinkedField", + "name": "session", + "plural": false, + "selections": [ + (v1/*: any*/), + (v4/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "deviceId", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "finishedAt", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null } ], "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "cursor", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "PageInfo", + "kind": "LinkedField", + "name": "pageInfo", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "endCursor", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "hasNextPage", + "storageKey": null } ], "storageKey": null } ], - "storageKey": "compatSsoLogins(first:10)" + "storageKey": null + }, + { + "alias": null, + "args": (v3/*: any*/), + "filters": null, + "handle": "connection", + "key": "CompatSsoLoginList_user_compatSsoLogins", + "kind": "LinkedHandle", + "name": "compatSsoLogins" } ], "storageKey": null @@ -136,16 +240,16 @@ return { ] }, "params": { - "cacheID": "1b4d3469bb6ccc19b8944b1f7fedd9c5", + "cacheID": "3543c7ada63831383f66dbeed4b1648e", "id": null, "metadata": {}, "name": "HomeQuery", "operationKind": "query", - "text": "query HomeQuery {\n currentUser {\n id\n username\n ...CompatSsoLoginList_user\n }\n}\n\nfragment CompatSsoLoginList_user on User {\n compatSsoLogins(first: 10) {\n edges {\n node {\n ...CompatSsoLogin_login\n id\n }\n }\n }\n}\n\nfragment CompatSsoLogin_login on CompatSsoLogin {\n id\n redirectUri\n}\n" + "text": "query HomeQuery(\n $count: Int!\n $cursor: String\n) {\n currentUser {\n id\n username\n ...CompatSsoLoginList_user\n }\n}\n\nfragment CompatSsoLoginList_user on User {\n compatSsoLogins(first: $count, after: $cursor) {\n edges {\n node {\n id\n ...CompatSsoLogin_login\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n id\n}\n\nfragment CompatSsoLogin_login on CompatSsoLogin {\n id\n redirectUri\n createdAt\n session {\n id\n createdAt\n deviceId\n finishedAt\n }\n}\n" } }; })(); -(node as any).hash = "a5defd2dc81b62abebfe7a393573d5a8"; +(node as any).hash = "1cd5ce8ae5a912b3d7ecf0c6e3cd8469"; export default node;