diff --git a/Cargo.lock b/Cargo.lock index 2619a250..4c7c73c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3224,6 +3224,7 @@ dependencies = [ "async-graphql", "async-trait", "chrono", + "lettre", "mas-data-model", "mas-storage", "oauth2-types", diff --git a/crates/graphql/Cargo.toml b/crates/graphql/Cargo.toml index 2cb50c20..1bb2a3f5 100644 --- a/crates/graphql/Cargo.toml +++ b/crates/graphql/Cargo.toml @@ -10,6 +10,7 @@ anyhow = "1.0.71" async-graphql = { version = "5.0.9", features = ["chrono", "url"] } async-trait = "0.1.68" chrono = "0.4.24" +lettre = { version = "0.10.4", default-features = false } serde = { version = "1.0.163", features = ["derive"] } thiserror = "1.0.40" tokio = { version = "1.28.1", features = ["sync"] } diff --git a/crates/graphql/src/mutations/user_email.rs b/crates/graphql/src/mutations/user_email.rs index c48e5b6f..5d060598 100644 --- a/crates/graphql/src/mutations/user_email.rs +++ b/crates/graphql/src/mutations/user_email.rs @@ -46,6 +46,8 @@ pub enum AddEmailStatus { Added, /// The email address already exists Exists, + /// The email address is invalid + Invalid, } /// The payload of the `addEmail` mutation @@ -53,6 +55,7 @@ pub enum AddEmailStatus { enum AddEmailPayload { Added(mas_data_model::UserEmail), Exists(mas_data_model::UserEmail), + Invalid, } #[Object(use_type_description)] @@ -62,25 +65,28 @@ impl AddEmailPayload { match self { AddEmailPayload::Added(_) => AddEmailStatus::Added, AddEmailPayload::Exists(_) => AddEmailStatus::Exists, + AddEmailPayload::Invalid => AddEmailStatus::Invalid, } } /// The email address that was added - async fn email(&self) -> UserEmail { + async fn email(&self) -> Option { match self { AddEmailPayload::Added(email) | AddEmailPayload::Exists(email) => { - UserEmail(email.clone()) + Some(UserEmail(email.clone())) } + AddEmailPayload::Invalid => None, } } /// The user to whom the email address was added - async fn user(&self, ctx: &Context<'_>) -> Result { + async fn user(&self, ctx: &Context<'_>) -> Result, async_graphql::Error> { let state = ctx.state(); let mut repo = state.repository().await?; let user_id = match self { AddEmailPayload::Added(email) | AddEmailPayload::Exists(email) => email.user_id, + AddEmailPayload::Invalid => return Ok(None), }; let user = repo @@ -89,7 +95,7 @@ impl AddEmailPayload { .await? .context("User not found")?; - Ok(User(user)) + Ok(Some(User(user))) } } @@ -227,6 +233,77 @@ impl VerifyEmailPayload { } } +/// The input for the `removeEmail` mutation +#[derive(InputObject)] +struct RemoveEmailInput { + /// The ID of the email address to remove + user_email_id: ID, +} + +/// The status of the `removeEmail` mutation +#[derive(Enum, Copy, Clone, Eq, PartialEq)] +enum RemoveEmailStatus { + /// The email address was removed + Removed, + + /// Can't remove the primary email address + Primary, + + /// The email address was not found + NotFound, +} + +/// The payload of the `removeEmail` mutation +#[derive(Description)] +enum RemoveEmailPayload { + Removed(mas_data_model::UserEmail), + Primary(mas_data_model::UserEmail), + NotFound, +} + +#[Object(use_type_description)] +impl RemoveEmailPayload { + /// Status of the operation + async fn status(&self) -> RemoveEmailStatus { + match self { + RemoveEmailPayload::Removed(_) => RemoveEmailStatus::Removed, + RemoveEmailPayload::Primary(_) => RemoveEmailStatus::Primary, + RemoveEmailPayload::NotFound => RemoveEmailStatus::NotFound, + } + } + + /// The email address that was removed + async fn email(&self) -> Option { + match self { + RemoveEmailPayload::Removed(email) | RemoveEmailPayload::Primary(email) => { + Some(UserEmail(email.clone())) + } + RemoveEmailPayload::NotFound => None, + } + } + + /// The user to whom the email address belonged + async fn user(&self, ctx: &Context<'_>) -> Result, async_graphql::Error> { + let state = ctx.state(); + let mut repo = state.repository().await?; + + let user_id = match self { + RemoveEmailPayload::Removed(email) | RemoveEmailPayload::Primary(email) => { + email.user_id + } + RemoveEmailPayload::NotFound => return Ok(None), + }; + + let user = repo + .user() + .lookup(user_id) + .await? + .context("User not found")?; + + Ok(Some(User(user))) + } +} + #[Object] impl UserEmailMutations { /// Add an email address to the specified user @@ -249,6 +326,12 @@ impl UserEmailMutations { // XXX: this logic should be extracted somewhere else, since most of it is // duplicated in mas_handlers + + // Validate the email address + if input.email.parse::().is_err() { + return Ok(AddEmailPayload::Invalid); + } + // Find an existing email address let existing_user_email = repo.user_email().find(user, &input.email).await?; let (added, user_email) = if let Some(user_email) = existing_user_email { @@ -388,4 +471,39 @@ impl UserEmailMutations { Ok(VerifyEmailPayload::Verified(user_email)) } + + /// Remove an email address + async fn remove_email( + &self, + ctx: &Context<'_>, + input: RemoveEmailInput, + ) -> Result { + let state = ctx.state(); + let user_email_id = NodeType::UserEmail.extract_ulid(&input.user_email_id)?; + let requester = ctx.requester(); + + let user = requester.user().context("Unauthorized")?; + + let mut repo = state.repository().await?; + + let user_email = repo.user_email().lookup(user_email_id).await?; + let Some(user_email) = user_email else { + return Ok(RemoveEmailPayload::NotFound); + }; + + if user_email.user_id != user.id { + return Err(async_graphql::Error::new("Unauthorized")); + } + + if user.primary_user_email_id == Some(user_email.id) { + // Prevent removing the primary email address + return Ok(RemoveEmailPayload::Primary(user_email)); + } + + repo.user_email().remove(user_email.clone()).await?; + + repo.save().await?; + + Ok(RemoveEmailPayload::Removed(user_email)) + } } diff --git a/frontend/schema.graphql b/frontend/schema.graphql index dac6df89..ba1c1fd4 100644 --- a/frontend/schema.graphql +++ b/frontend/schema.graphql @@ -23,11 +23,11 @@ type AddEmailPayload { """ The email address that was added """ - email: UserEmail! + email: UserEmail """ The user to whom the email address was added """ - user: User! + user: User } """ @@ -42,6 +42,10 @@ enum AddEmailStatus { The email address already exists """ EXISTS + """ + The email address is invalid + """ + INVALID } type Anonymous implements Node { @@ -237,6 +241,10 @@ type Mutation { Submit a verification code for an email address """ verifyEmail(input: VerifyEmailInput!): VerifyEmailPayload! + """ + Remove an email address + """ + removeEmail(input: RemoveEmailInput!): RemoveEmailPayload! } """ @@ -421,6 +429,52 @@ type Query { viewerSession: ViewerSession! } +""" +The input for the `removeEmail` mutation +""" +input RemoveEmailInput { + """ + The ID of the email address to remove + """ + userEmailId: ID! +} + +""" +The payload of the `removeEmail` mutation +""" +type RemoveEmailPayload { + """ + Status of the operation + """ + status: RemoveEmailStatus! + """ + The email address that was removed + """ + email: UserEmail + """ + The user to whom the email address belonged + """ + user: User +} + +""" +The status of the `removeEmail` mutation +""" +enum RemoveEmailStatus { + """ + The email address was removed + """ + REMOVED + """ + Can't remove the primary email address + """ + PRIMARY + """ + The email address was not found + """ + NOT_FOUND +} + """ The input for the `sendVerificationEmail` mutation """ diff --git a/frontend/src/components/AddEmailForm.tsx b/frontend/src/components/AddEmailForm.tsx index 8eb85929..0c31554f 100644 --- a/frontend/src/components/AddEmailForm.tsx +++ b/frontend/src/components/AddEmailForm.tsx @@ -58,7 +58,12 @@ const AddEmailForm: React.FC<{ userId: string }> = ({ userId }) => { const formData = new FormData(e.currentTarget); const email = formData.get("email") as string; startTransition(() => { - addEmail({ userId, email }).then(() => { + addEmail({ userId, email }).then((result) => { + // Don't clear the form if the email was invalid + if (result.data?.addEmail.status === "INVALID") { + return; + } + startTransition(() => { // Paginate to the last page setCurrentPagination(LAST_PAGE); @@ -74,22 +79,33 @@ const AddEmailForm: React.FC<{ userId: string }> = ({ userId }) => { }); }; + const status = addEmailResult.data?.addEmail.status ?? null; + const emailAdded = status === "ADDED"; + const emailExists = status === "EXISTS"; + const emailInvalid = status === "INVALID"; + return ( <> - {addEmailResult.data?.addEmail.status === "ADDED" && ( - <> -
- Email added! -
- + {emailAdded && ( +
+ Email added! +
)} - {addEmailResult.data?.addEmail.status === "EXISTS" && ( - <> -
- Email already exists! -
- + + {emailExists && ( +
+ Email already exists! +
)} + + {emailInvalid && ( +
+ + Invalid email address + +
+ )} +
((get) => ({ - first: get(pageSizeAtom), - after: null, -})); +const currentPaginationAtom = atomForCurrentPagination(); const browserSessionListFamily = atomFamily((userId: string) => { const browserSessionList = atomWithQuery({ query: QUERY, - getVariables: (get) => ({ userId, ...get(currentPagination) }), + getVariables: (get) => ({ userId, ...get(currentPaginationAtom) }), }); return browserSessionList; }); @@ -85,7 +86,7 @@ const pageInfoFamily = atomFamily((userId: string) => { const paginationFamily = atomFamily((userId: string) => { const paginationAtom = atomWithPagination( - currentPagination, + currentPaginationAtom, pageInfoFamily(userId) ); @@ -96,7 +97,7 @@ const BrowserSessionList: React.FC<{ userId: string }> = ({ userId }) => { const currentSessionId = useAtomValue(currentBrowserSessionIdAtom); const [pending, startTransition] = useTransition(); const result = useAtomValue(browserSessionListFamily(userId)); - const setPagination = useSetAtom(currentPagination); + const setPagination = useSetAtom(currentPaginationAtom); const [prevPage, nextPage] = useAtomValue(paginationFamily(userId)); const paginate = (pagination: Pagination) => { diff --git a/frontend/src/components/CompatSsoLoginList.tsx b/frontend/src/components/CompatSsoLoginList.tsx index 0cd58173..cb0a544f 100644 --- a/frontend/src/components/CompatSsoLoginList.tsx +++ b/frontend/src/components/CompatSsoLoginList.tsx @@ -13,13 +13,17 @@ // limitations under the License. import { atom, useSetAtom, useAtomValue } from "jotai"; -import { atomFamily, atomWithDefault } from "jotai/utils"; +import { atomFamily } from "jotai/utils"; import { atomWithQuery } from "jotai-urql"; import { useTransition } from "react"; import { graphql } from "../gql"; import { PageInfo } from "../gql/graphql"; -import { atomWithPagination, pageSizeAtom, Pagination } from "../pagination"; +import { + atomForCurrentPagination, + atomWithPagination, + Pagination, +} from "../pagination"; import BlockList from "./BlockList"; import CompatSsoLogin from "./CompatSsoLogin"; @@ -60,15 +64,12 @@ const QUERY = graphql(/* GraphQL */ ` } `); -const currentPagination = atomWithDefault((get) => ({ - first: get(pageSizeAtom), - after: null, -})); +const currentPaginationAtom = atomForCurrentPagination(); const compatSsoLoginListFamily = atomFamily((userId: string) => { const compatSsoLoginList = atomWithQuery({ query: QUERY, - getVariables: (get) => ({ userId, ...get(currentPagination) }), + getVariables: (get) => ({ userId, ...get(currentPaginationAtom) }), }); return compatSsoLoginList; @@ -85,7 +86,7 @@ const pageInfoFamily = atomFamily((userId: string) => { const paginationFamily = atomFamily((userId: string) => { const paginationAtom = atomWithPagination( - currentPagination, + currentPaginationAtom, pageInfoFamily(userId) ); return paginationAtom; @@ -94,7 +95,7 @@ const paginationFamily = atomFamily((userId: string) => { const CompatSsoLoginList: React.FC<{ userId: string }> = ({ userId }) => { const [pending, startTransition] = useTransition(); const result = useAtomValue(compatSsoLoginListFamily(userId)); - const setPagination = useSetAtom(currentPagination); + const setPagination = useSetAtom(currentPaginationAtom); const [prevPage, nextPage] = useAtomValue(paginationFamily(userId)); const paginate = (pagination: Pagination) => { diff --git a/frontend/src/components/OAuth2SessionList.tsx b/frontend/src/components/OAuth2SessionList.tsx index b17962d4..ecbd0959 100644 --- a/frontend/src/components/OAuth2SessionList.tsx +++ b/frontend/src/components/OAuth2SessionList.tsx @@ -13,13 +13,17 @@ // limitations under the License. import { useAtomValue, atom, useSetAtom } from "jotai"; -import { atomFamily, atomWithDefault } from "jotai/utils"; +import { atomFamily } from "jotai/utils"; import { atomWithQuery } from "jotai-urql"; import { useTransition } from "react"; import { graphql } from "../gql"; import { PageInfo } from "../gql/graphql"; -import { atomWithPagination, pageSizeAtom, Pagination } from "../pagination"; +import { + atomForCurrentPagination, + atomWithPagination, + Pagination, +} from "../pagination"; import BlockList from "./BlockList"; import OAuth2Session from "./OAuth2Session"; @@ -61,15 +65,12 @@ const QUERY = graphql(/* GraphQL */ ` } `); -const currentPagination = atomWithDefault((get) => ({ - first: get(pageSizeAtom), - after: null, -})); +const currentPaginationAtom = atomForCurrentPagination(); const oauth2SessionListFamily = atomFamily((userId: string) => { const oauth2SessionList = atomWithQuery({ query: QUERY, - getVariables: (get) => ({ userId, ...get(currentPagination) }), + getVariables: (get) => ({ userId, ...get(currentPaginationAtom) }), }); return oauth2SessionList; @@ -86,7 +87,7 @@ const pageInfoFamily = atomFamily((userId: string) => { const paginationFamily = atomFamily((userId: string) => { const paginationAtom = atomWithPagination( - currentPagination, + currentPaginationAtom, pageInfoFamily(userId) ); return paginationAtom; @@ -99,7 +100,7 @@ type Props = { const OAuth2SessionList: React.FC = ({ userId }) => { const [pending, startTransition] = useTransition(); const result = useAtomValue(oauth2SessionListFamily(userId)); - const setPagination = useSetAtom(currentPagination); + const setPagination = useSetAtom(currentPaginationAtom); const [prevPage, nextPage] = useAtomValue(paginationFamily(userId)); const paginate = (pagination: Pagination) => { diff --git a/frontend/src/components/UserEmail.tsx b/frontend/src/components/UserEmail.tsx index 24a21183..afb59520 100644 --- a/frontend/src/components/UserEmail.tsx +++ b/frontend/src/components/UserEmail.tsx @@ -24,6 +24,7 @@ import Button from "./Button"; import DateTime from "./DateTime"; import Input from "./Input"; import Typography, { Bold } from "./Typography"; +import { emailPageResultFamily } from "./UserEmailList"; const FRAGMENT = graphql(/* GraphQL */ ` fragment UserEmail_email on UserEmail { @@ -74,6 +75,18 @@ const RESEND_VERIFICATION_EMAIL_MUTATION = graphql(/* GraphQL */ ` } `); +const REMOVE_EMAIL_MUTATION = graphql(/* GraphQL */ ` + mutation RemoveEmail($id: ID!) { + removeEmail(input: { userEmailId: $id }) { + status + + user { + id + } + } + } +`); + const verifyEmailFamily = atomFamily((id: string) => { const verifyEmail = atomWithMutation(VERIFY_EMAIL_MUTATION); @@ -100,19 +113,35 @@ const resendVerificationEmailFamily = atomFamily((id: string) => { return resendVerificationEmailAtom; }); +const removeEmailFamily = atomFamily((id: string) => { + const removeEmail = atomWithMutation(REMOVE_EMAIL_MUTATION); + + // A proxy atom which pre-sets the id variable in the mutation + const removeEmailAtom = atom( + (get) => get(removeEmail), + (get, set) => set(removeEmail, { id }) + ); + + return removeEmailAtom; +}); + const UserEmail: React.FC<{ email: FragmentType; + userId: string; // XXX: Can we get this from the fragment? isPrimary?: boolean; highlight?: boolean; -}> = ({ email, isPrimary, highlight }) => { +}> = ({ email, userId, isPrimary, highlight }) => { const [pending, startTransition] = useTransition(); const data = useFragment(FRAGMENT, email); const [verifyEmailResult, verifyEmail] = useAtom(verifyEmailFamily(data.id)); const [resendVerificationEmailResult, resendVerificationEmail] = useAtom( resendVerificationEmailFamily(data.id) ); + const resetEmailPage = useSetAtom(emailPageResultFamily(userId)); + const removeEmail = useSetAtom(removeEmailFamily(data.id)); const formRef = useRef(null); + // XXX: we probably want those callbacks passed in props instead const onFormSubmit = (e: React.FormEvent) => { e.preventDefault(); const formData = new FormData(e.currentTarget); @@ -132,8 +161,20 @@ const UserEmail: React.FC<{ }); }; + const onRemoveClick = () => { + startTransition(() => { + removeEmail().then(() => { + // XXX: this forces a re-fetch of the user's emails, + // but maybe it's not the right place to do this + resetEmailPage(); + }); + }); + }; + const emailSent = resendVerificationEmailResult.data?.sendVerificationEmail.status === "SENT"; + const invalidCode = + verifyEmailResult.data?.verifyEmail.status === "INVALID_CODE"; return ( @@ -142,9 +183,16 @@ const UserEmail: React.FC<{ Primary )} - - {data.email} - +
+ + {data.email} + + {!isPrimary && ( + + )} +
{data.confirmedAt ? ( Verified @@ -162,6 +210,9 @@ const UserEmail: React.FC<{ type="text" inputMode="numeric" /> + {invalidCode && ( +
Invalid code
+ )} diff --git a/frontend/src/components/UserEmailList.tsx b/frontend/src/components/UserEmailList.tsx index 09a079dd..5ce12920 100644 --- a/frontend/src/components/UserEmailList.tsx +++ b/frontend/src/components/UserEmailList.tsx @@ -13,7 +13,7 @@ // limitations under the License. import { atom, useAtomValue, useSetAtom } from "jotai"; -import { atomFamily, atomWithDefault } from "jotai/utils"; +import { atomFamily } from "jotai/utils"; import { atomWithQuery } from "jotai-urql"; import { useTransition } from "react"; @@ -22,7 +22,6 @@ import { PageInfo } from "../gql/graphql"; import { atomForCurrentPagination, atomWithPagination, - pageSizeAtom, Pagination, } from "../pagination"; @@ -143,6 +142,7 @@ const UserEmailList: React.FC<{ {result.data?.user?.emails?.edges?.map((edge) => ( ; /** Status of the operation */ status: AddEmailStatus; /** The user to whom the email address was added */ - user: User; + user?: Maybe; }; /** The status of the `addEmail` mutation */ @@ -53,6 +53,8 @@ export enum AddEmailStatus { Added = "ADDED", /** The email address already exists */ Exists = "EXISTS", + /** The email address is invalid */ + Invalid = "INVALID", } export type Anonymous = Node & { @@ -178,6 +180,8 @@ export type Mutation = { __typename?: "Mutation"; /** Add an email address to the specified user */ addEmail: AddEmailPayload; + /** Remove an email address */ + removeEmail: RemoveEmailPayload; /** Send a verification code for an email address */ sendVerificationEmail: SendVerificationEmailPayload; /** Submit a verification code for an email address */ @@ -189,6 +193,11 @@ export type MutationAddEmailArgs = { input: AddEmailInput; }; +/** The mutations root of the GraphQL interface. */ +export type MutationRemoveEmailArgs = { + input: RemoveEmailInput; +}; + /** The mutations root of the GraphQL interface. */ export type MutationSendVerificationEmailArgs = { input: SendVerificationEmailInput; @@ -352,6 +361,33 @@ export type QueryUserEmailArgs = { id: Scalars["ID"]; }; +/** The input for the `removeEmail` mutation */ +export type RemoveEmailInput = { + /** The ID of the email address to remove */ + userEmailId: Scalars["ID"]; +}; + +/** The payload of the `removeEmail` mutation */ +export type RemoveEmailPayload = { + __typename?: "RemoveEmailPayload"; + /** The email address that was removed */ + email?: Maybe; + /** Status of the operation */ + status: RemoveEmailStatus; + /** The user to whom the email address belonged */ + user?: Maybe; +}; + +/** The status of the `removeEmail` mutation */ +export enum RemoveEmailStatus { + /** The email address was not found */ + NotFound = "NOT_FOUND", + /** Can't remove the primary email address */ + Primary = "PRIMARY", + /** The email address was removed */ + Removed = "REMOVED", +} + /** The input for the `sendVerificationEmail` mutation */ export type SendVerificationEmailInput = { /** The ID of the email address to verify */ @@ -607,9 +643,13 @@ export type AddEmailMutation = { addEmail: { __typename?: "AddEmailPayload"; status: AddEmailStatus; - email: { __typename?: "UserEmail"; id: string } & { - " $fragmentRefs"?: { UserEmail_EmailFragment: UserEmail_EmailFragment }; - }; + email?: + | ({ __typename?: "UserEmail"; id: string } & { + " $fragmentRefs"?: { + UserEmail_EmailFragment: UserEmail_EmailFragment; + }; + }) + | null; }; }; @@ -808,6 +848,19 @@ export type ResendVerificationEmailMutation = { }; }; +export type RemoveEmailMutationVariables = Exact<{ + id: Scalars["ID"]; +}>; + +export type RemoveEmailMutation = { + __typename?: "Mutation"; + removeEmail: { + __typename?: "RemoveEmailPayload"; + status: RemoveEmailStatus; + user?: { __typename?: "User"; id: string } | null; + }; +}; + export type UserEmailListQueryQueryVariables = Exact<{ userId: Scalars["ID"]; first?: InputMaybe; @@ -2101,6 +2154,70 @@ export const ResendVerificationEmailDocument = { ResendVerificationEmailMutation, ResendVerificationEmailMutationVariables >; +export const RemoveEmailDocument = { + kind: "Document", + definitions: [ + { + kind: "OperationDefinition", + operation: "mutation", + name: { kind: "Name", value: "RemoveEmail" }, + variableDefinitions: [ + { + kind: "VariableDefinition", + variable: { kind: "Variable", name: { kind: "Name", value: "id" } }, + type: { + kind: "NonNullType", + type: { kind: "NamedType", name: { kind: "Name", value: "ID" } }, + }, + }, + ], + selectionSet: { + kind: "SelectionSet", + selections: [ + { + kind: "Field", + name: { kind: "Name", value: "removeEmail" }, + arguments: [ + { + kind: "Argument", + name: { kind: "Name", value: "input" }, + value: { + kind: "ObjectValue", + fields: [ + { + kind: "ObjectField", + name: { kind: "Name", value: "userEmailId" }, + value: { + kind: "Variable", + name: { kind: "Name", value: "id" }, + }, + }, + ], + }, + }, + ], + selectionSet: { + kind: "SelectionSet", + selections: [ + { kind: "Field", name: { kind: "Name", value: "status" } }, + { + kind: "Field", + name: { kind: "Name", value: "user" }, + selectionSet: { + kind: "SelectionSet", + selections: [ + { kind: "Field", name: { kind: "Name", value: "id" } }, + ], + }, + }, + ], + }, + }, + ], + }, + }, + ], +} as unknown as DocumentNode; export const UserEmailListQueryDocument = { kind: "Document", definitions: [ diff --git a/frontend/src/gql/schema.ts b/frontend/src/gql/schema.ts index fca30436..1e334648 100644 --- a/frontend/src/gql/schema.ts +++ b/frontend/src/gql/schema.ts @@ -1,1995 +1,2048 @@ /* eslint-disable */ -import { IntrospectionQuery } from 'graphql'; +import { IntrospectionQuery } from "graphql"; export default { - "__schema": { - "queryType": { - "name": "Query" + __schema: { + queryType: { + name: "Query", }, - "mutationType": { - "name": "Mutation" + mutationType: { + name: "Mutation", }, - "subscriptionType": null, - "types": [ + subscriptionType: null, + types: [ { - "kind": "OBJECT", - "name": "AddEmailPayload", - "fields": [ + kind: "OBJECT", + name: "AddEmailPayload", + fields: [ { - "name": "email", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "OBJECT", - "name": "UserEmail", - "ofType": null - } + name: "email", + type: { + kind: "OBJECT", + name: "UserEmail", + ofType: null, }, - "args": [] + args: [], }, { - "name": "status", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } + name: "status", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, }, - "args": [] + args: [], }, { - "name": "user", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "OBJECT", - "name": "User", - "ofType": null - } + name: "user", + type: { + kind: "OBJECT", + name: "User", + ofType: null, }, - "args": [] - } + args: [], + }, ], - "interfaces": [] + interfaces: [], }, { - "kind": "OBJECT", - "name": "Anonymous", - "fields": [ + kind: "OBJECT", + name: "Anonymous", + fields: [ { - "name": "id", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } + name: "id", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, }, - "args": [] - } + args: [], + }, ], - "interfaces": [ + interfaces: [ { - "kind": "INTERFACE", - "name": "Node" - } - ] + kind: "INTERFACE", + name: "Node", + }, + ], }, { - "kind": "OBJECT", - "name": "Authentication", - "fields": [ + kind: "OBJECT", + name: "Authentication", + fields: [ { - "name": "createdAt", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } + name: "createdAt", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, }, - "args": [] + args: [], }, { - "name": "id", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } + name: "id", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, }, - "args": [] - } + args: [], + }, ], - "interfaces": [ + interfaces: [ { - "kind": "INTERFACE", - "name": "CreationEvent" + kind: "INTERFACE", + name: "CreationEvent", }, { - "kind": "INTERFACE", - "name": "Node" - } - ] + kind: "INTERFACE", + name: "Node", + }, + ], }, { - "kind": "OBJECT", - "name": "BrowserSession", - "fields": [ + kind: "OBJECT", + name: "BrowserSession", + fields: [ { - "name": "createdAt", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } + name: "createdAt", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, }, - "args": [] + args: [], }, { - "name": "id", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } + name: "id", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, }, - "args": [] + args: [], }, { - "name": "lastAuthentication", - "type": { - "kind": "OBJECT", - "name": "Authentication", - "ofType": null + name: "lastAuthentication", + type: { + kind: "OBJECT", + name: "Authentication", + ofType: null, }, - "args": [] + args: [], }, { - "name": "user", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "OBJECT", - "name": "User", - "ofType": null - } + name: "user", + type: { + kind: "NON_NULL", + ofType: { + kind: "OBJECT", + name: "User", + ofType: null, + }, }, - "args": [] - } + args: [], + }, ], - "interfaces": [ + interfaces: [ { - "kind": "INTERFACE", - "name": "CreationEvent" + kind: "INTERFACE", + name: "CreationEvent", }, { - "kind": "INTERFACE", - "name": "Node" - } - ] + kind: "INTERFACE", + name: "Node", + }, + ], }, { - "kind": "OBJECT", - "name": "BrowserSessionConnection", - "fields": [ + kind: "OBJECT", + name: "BrowserSessionConnection", + fields: [ { - "name": "edges", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "LIST", - "ofType": { - "kind": "NON_NULL", - "ofType": { - "kind": "OBJECT", - "name": "BrowserSessionEdge", - "ofType": null - } - } - } + name: "edges", + type: { + kind: "NON_NULL", + ofType: { + kind: "LIST", + ofType: { + kind: "NON_NULL", + ofType: { + kind: "OBJECT", + name: "BrowserSessionEdge", + ofType: null, + }, + }, + }, }, - "args": [] + args: [], }, { - "name": "nodes", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "LIST", - "ofType": { - "kind": "NON_NULL", - "ofType": { - "kind": "OBJECT", - "name": "BrowserSession", - "ofType": null - } - } - } + name: "nodes", + type: { + kind: "NON_NULL", + ofType: { + kind: "LIST", + ofType: { + kind: "NON_NULL", + ofType: { + kind: "OBJECT", + name: "BrowserSession", + ofType: null, + }, + }, + }, }, - "args": [] + args: [], }, { - "name": "pageInfo", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "OBJECT", - "name": "PageInfo", - "ofType": null - } + name: "pageInfo", + type: { + kind: "NON_NULL", + ofType: { + kind: "OBJECT", + name: "PageInfo", + ofType: null, + }, }, - "args": [] - } + args: [], + }, ], - "interfaces": [] + interfaces: [], }, { - "kind": "OBJECT", - "name": "BrowserSessionEdge", - "fields": [ + kind: "OBJECT", + name: "BrowserSessionEdge", + fields: [ { - "name": "cursor", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } + name: "cursor", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, }, - "args": [] + args: [], }, { - "name": "node", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "OBJECT", - "name": "BrowserSession", - "ofType": null - } + name: "node", + type: { + kind: "NON_NULL", + ofType: { + kind: "OBJECT", + name: "BrowserSession", + ofType: null, + }, }, - "args": [] - } + args: [], + }, ], - "interfaces": [] + interfaces: [], }, { - "kind": "OBJECT", - "name": "CompatSession", - "fields": [ + kind: "OBJECT", + name: "CompatSession", + fields: [ { - "name": "createdAt", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } + name: "createdAt", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, }, - "args": [] + args: [], }, { - "name": "deviceId", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } + name: "deviceId", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, }, - "args": [] + args: [], }, { - "name": "finishedAt", - "type": { - "kind": "SCALAR", - "name": "Any" + name: "finishedAt", + type: { + kind: "SCALAR", + name: "Any", }, - "args": [] + args: [], }, { - "name": "id", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } + name: "id", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, }, - "args": [] + args: [], }, { - "name": "user", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "OBJECT", - "name": "User", - "ofType": null - } + name: "user", + type: { + kind: "NON_NULL", + ofType: { + kind: "OBJECT", + name: "User", + ofType: null, + }, }, - "args": [] - } + args: [], + }, ], - "interfaces": [ + interfaces: [ { - "kind": "INTERFACE", - "name": "CreationEvent" + kind: "INTERFACE", + name: "CreationEvent", }, { - "kind": "INTERFACE", - "name": "Node" - } - ] + kind: "INTERFACE", + name: "Node", + }, + ], }, { - "kind": "OBJECT", - "name": "CompatSsoLogin", - "fields": [ + kind: "OBJECT", + name: "CompatSsoLogin", + fields: [ { - "name": "createdAt", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } + name: "createdAt", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, }, - "args": [] + args: [], }, { - "name": "exchangedAt", - "type": { - "kind": "SCALAR", - "name": "Any" + name: "exchangedAt", + type: { + kind: "SCALAR", + name: "Any", }, - "args": [] + args: [], }, { - "name": "fulfilledAt", - "type": { - "kind": "SCALAR", - "name": "Any" + name: "fulfilledAt", + type: { + kind: "SCALAR", + name: "Any", }, - "args": [] + args: [], }, { - "name": "id", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } + name: "id", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, }, - "args": [] + args: [], }, { - "name": "redirectUri", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } + name: "redirectUri", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, }, - "args": [] + args: [], }, { - "name": "session", - "type": { - "kind": "OBJECT", - "name": "CompatSession", - "ofType": null + name: "session", + type: { + kind: "OBJECT", + name: "CompatSession", + ofType: null, }, - "args": [] - } + args: [], + }, ], - "interfaces": [ + interfaces: [ { - "kind": "INTERFACE", - "name": "Node" - } - ] + kind: "INTERFACE", + name: "Node", + }, + ], }, { - "kind": "OBJECT", - "name": "CompatSsoLoginConnection", - "fields": [ + kind: "OBJECT", + name: "CompatSsoLoginConnection", + fields: [ { - "name": "edges", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "LIST", - "ofType": { - "kind": "NON_NULL", - "ofType": { - "kind": "OBJECT", - "name": "CompatSsoLoginEdge", - "ofType": null - } - } - } + name: "edges", + type: { + kind: "NON_NULL", + ofType: { + kind: "LIST", + ofType: { + kind: "NON_NULL", + ofType: { + kind: "OBJECT", + name: "CompatSsoLoginEdge", + ofType: null, + }, + }, + }, }, - "args": [] + args: [], }, { - "name": "nodes", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "LIST", - "ofType": { - "kind": "NON_NULL", - "ofType": { - "kind": "OBJECT", - "name": "CompatSsoLogin", - "ofType": null - } - } - } + name: "nodes", + type: { + kind: "NON_NULL", + ofType: { + kind: "LIST", + ofType: { + kind: "NON_NULL", + ofType: { + kind: "OBJECT", + name: "CompatSsoLogin", + ofType: null, + }, + }, + }, }, - "args": [] + args: [], }, { - "name": "pageInfo", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "OBJECT", - "name": "PageInfo", - "ofType": null - } + name: "pageInfo", + type: { + kind: "NON_NULL", + ofType: { + kind: "OBJECT", + name: "PageInfo", + ofType: null, + }, }, - "args": [] - } + args: [], + }, ], - "interfaces": [] + interfaces: [], }, { - "kind": "OBJECT", - "name": "CompatSsoLoginEdge", - "fields": [ + kind: "OBJECT", + name: "CompatSsoLoginEdge", + fields: [ { - "name": "cursor", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } + name: "cursor", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, }, - "args": [] + args: [], }, { - "name": "node", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "OBJECT", - "name": "CompatSsoLogin", - "ofType": null - } + name: "node", + type: { + kind: "NON_NULL", + ofType: { + kind: "OBJECT", + name: "CompatSsoLogin", + ofType: null, + }, }, - "args": [] - } + args: [], + }, ], - "interfaces": [] + interfaces: [], }, { - "kind": "INTERFACE", - "name": "CreationEvent", - "fields": [ + kind: "INTERFACE", + name: "CreationEvent", + fields: [ { - "name": "createdAt", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } + name: "createdAt", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, }, - "args": [] - } + args: [], + }, ], - "interfaces": [], - "possibleTypes": [ + interfaces: [], + possibleTypes: [ { - "kind": "OBJECT", - "name": "Authentication" + kind: "OBJECT", + name: "Authentication", }, { - "kind": "OBJECT", - "name": "BrowserSession" + kind: "OBJECT", + name: "BrowserSession", }, { - "kind": "OBJECT", - "name": "CompatSession" + kind: "OBJECT", + name: "CompatSession", }, { - "kind": "OBJECT", - "name": "UpstreamOAuth2Link" + kind: "OBJECT", + name: "UpstreamOAuth2Link", }, { - "kind": "OBJECT", - "name": "UpstreamOAuth2Provider" + kind: "OBJECT", + name: "UpstreamOAuth2Provider", }, { - "kind": "OBJECT", - "name": "UserEmail" - } - ] + kind: "OBJECT", + name: "UserEmail", + }, + ], }, { - "kind": "OBJECT", - "name": "Mutation", - "fields": [ + kind: "OBJECT", + name: "Mutation", + fields: [ { - "name": "addEmail", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "OBJECT", - "name": "AddEmailPayload", - "ofType": null - } + name: "addEmail", + type: { + kind: "NON_NULL", + ofType: { + kind: "OBJECT", + name: "AddEmailPayload", + ofType: null, + }, }, - "args": [ + args: [ { - "name": "input", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } - } - } - ] + name: "input", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, + }, + }, + ], }, { - "name": "sendVerificationEmail", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "OBJECT", - "name": "SendVerificationEmailPayload", - "ofType": null - } + name: "removeEmail", + type: { + kind: "NON_NULL", + ofType: { + kind: "OBJECT", + name: "RemoveEmailPayload", + ofType: null, + }, }, - "args": [ + args: [ { - "name": "input", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } - } - } - ] + name: "input", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, + }, + }, + ], }, { - "name": "verifyEmail", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "OBJECT", - "name": "VerifyEmailPayload", - "ofType": null - } + name: "sendVerificationEmail", + type: { + kind: "NON_NULL", + ofType: { + kind: "OBJECT", + name: "SendVerificationEmailPayload", + ofType: null, + }, }, - "args": [ + args: [ { - "name": "input", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } - } - } - ] - } + name: "input", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, + }, + }, + ], + }, + { + name: "verifyEmail", + type: { + kind: "NON_NULL", + ofType: { + kind: "OBJECT", + name: "VerifyEmailPayload", + ofType: null, + }, + }, + args: [ + { + name: "input", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, + }, + }, + ], + }, ], - "interfaces": [] + interfaces: [], }, { - "kind": "INTERFACE", - "name": "Node", - "fields": [ + kind: "INTERFACE", + name: "Node", + fields: [ { - "name": "id", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } + name: "id", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, }, - "args": [] - } + args: [], + }, ], - "interfaces": [], - "possibleTypes": [ + interfaces: [], + possibleTypes: [ { - "kind": "OBJECT", - "name": "Anonymous" + kind: "OBJECT", + name: "Anonymous", }, { - "kind": "OBJECT", - "name": "Authentication" + kind: "OBJECT", + name: "Authentication", }, { - "kind": "OBJECT", - "name": "BrowserSession" + kind: "OBJECT", + name: "BrowserSession", }, { - "kind": "OBJECT", - "name": "CompatSession" + kind: "OBJECT", + name: "CompatSession", }, { - "kind": "OBJECT", - "name": "CompatSsoLogin" + kind: "OBJECT", + name: "CompatSsoLogin", }, { - "kind": "OBJECT", - "name": "Oauth2Client" + kind: "OBJECT", + name: "Oauth2Client", }, { - "kind": "OBJECT", - "name": "Oauth2Session" + kind: "OBJECT", + name: "Oauth2Session", }, { - "kind": "OBJECT", - "name": "UpstreamOAuth2Link" + kind: "OBJECT", + name: "UpstreamOAuth2Link", }, { - "kind": "OBJECT", - "name": "UpstreamOAuth2Provider" + kind: "OBJECT", + name: "UpstreamOAuth2Provider", }, { - "kind": "OBJECT", - "name": "User" + kind: "OBJECT", + name: "User", }, { - "kind": "OBJECT", - "name": "UserEmail" - } - ] + kind: "OBJECT", + name: "UserEmail", + }, + ], }, { - "kind": "OBJECT", - "name": "Oauth2Client", - "fields": [ + kind: "OBJECT", + name: "Oauth2Client", + fields: [ { - "name": "clientId", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } + name: "clientId", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, }, - "args": [] + args: [], }, { - "name": "clientName", - "type": { - "kind": "SCALAR", - "name": "Any" + name: "clientName", + type: { + kind: "SCALAR", + name: "Any", }, - "args": [] + args: [], }, { - "name": "clientUri", - "type": { - "kind": "SCALAR", - "name": "Any" + name: "clientUri", + type: { + kind: "SCALAR", + name: "Any", }, - "args": [] + args: [], }, { - "name": "id", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } + name: "id", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, }, - "args": [] + args: [], }, { - "name": "policyUri", - "type": { - "kind": "SCALAR", - "name": "Any" + name: "policyUri", + type: { + kind: "SCALAR", + name: "Any", }, - "args": [] + args: [], }, { - "name": "redirectUris", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "LIST", - "ofType": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } - } - } + name: "redirectUris", + type: { + kind: "NON_NULL", + ofType: { + kind: "LIST", + ofType: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, + }, + }, }, - "args": [] + args: [], }, { - "name": "tosUri", - "type": { - "kind": "SCALAR", - "name": "Any" + name: "tosUri", + type: { + kind: "SCALAR", + name: "Any", }, - "args": [] - } + args: [], + }, ], - "interfaces": [ + interfaces: [ { - "kind": "INTERFACE", - "name": "Node" - } - ] + kind: "INTERFACE", + name: "Node", + }, + ], }, { - "kind": "OBJECT", - "name": "Oauth2Session", - "fields": [ + kind: "OBJECT", + name: "Oauth2Session", + fields: [ { - "name": "browserSession", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "OBJECT", - "name": "BrowserSession", - "ofType": null - } + name: "browserSession", + type: { + kind: "NON_NULL", + ofType: { + kind: "OBJECT", + name: "BrowserSession", + ofType: null, + }, }, - "args": [] + args: [], }, { - "name": "client", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "OBJECT", - "name": "Oauth2Client", - "ofType": null - } + name: "client", + type: { + kind: "NON_NULL", + ofType: { + kind: "OBJECT", + name: "Oauth2Client", + ofType: null, + }, }, - "args": [] + args: [], }, { - "name": "id", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } + name: "id", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, }, - "args": [] + args: [], }, { - "name": "scope", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } + name: "scope", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, }, - "args": [] + args: [], }, { - "name": "user", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "OBJECT", - "name": "User", - "ofType": null - } + name: "user", + type: { + kind: "NON_NULL", + ofType: { + kind: "OBJECT", + name: "User", + ofType: null, + }, }, - "args": [] - } + args: [], + }, ], - "interfaces": [ + interfaces: [ { - "kind": "INTERFACE", - "name": "Node" - } - ] + kind: "INTERFACE", + name: "Node", + }, + ], }, { - "kind": "OBJECT", - "name": "Oauth2SessionConnection", - "fields": [ + kind: "OBJECT", + name: "Oauth2SessionConnection", + fields: [ { - "name": "edges", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "LIST", - "ofType": { - "kind": "NON_NULL", - "ofType": { - "kind": "OBJECT", - "name": "Oauth2SessionEdge", - "ofType": null - } - } - } + name: "edges", + type: { + kind: "NON_NULL", + ofType: { + kind: "LIST", + ofType: { + kind: "NON_NULL", + ofType: { + kind: "OBJECT", + name: "Oauth2SessionEdge", + ofType: null, + }, + }, + }, }, - "args": [] + args: [], }, { - "name": "nodes", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "LIST", - "ofType": { - "kind": "NON_NULL", - "ofType": { - "kind": "OBJECT", - "name": "Oauth2Session", - "ofType": null - } - } - } + name: "nodes", + type: { + kind: "NON_NULL", + ofType: { + kind: "LIST", + ofType: { + kind: "NON_NULL", + ofType: { + kind: "OBJECT", + name: "Oauth2Session", + ofType: null, + }, + }, + }, }, - "args": [] + args: [], }, { - "name": "pageInfo", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "OBJECT", - "name": "PageInfo", - "ofType": null - } + name: "pageInfo", + type: { + kind: "NON_NULL", + ofType: { + kind: "OBJECT", + name: "PageInfo", + ofType: null, + }, }, - "args": [] - } + args: [], + }, ], - "interfaces": [] + interfaces: [], }, { - "kind": "OBJECT", - "name": "Oauth2SessionEdge", - "fields": [ + kind: "OBJECT", + name: "Oauth2SessionEdge", + fields: [ { - "name": "cursor", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } + name: "cursor", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, }, - "args": [] + args: [], }, { - "name": "node", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "OBJECT", - "name": "Oauth2Session", - "ofType": null - } + name: "node", + type: { + kind: "NON_NULL", + ofType: { + kind: "OBJECT", + name: "Oauth2Session", + ofType: null, + }, }, - "args": [] - } + args: [], + }, ], - "interfaces": [] + interfaces: [], }, { - "kind": "OBJECT", - "name": "PageInfo", - "fields": [ + kind: "OBJECT", + name: "PageInfo", + fields: [ { - "name": "endCursor", - "type": { - "kind": "SCALAR", - "name": "Any" + name: "endCursor", + type: { + kind: "SCALAR", + name: "Any", }, - "args": [] + args: [], }, { - "name": "hasNextPage", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } + name: "hasNextPage", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, }, - "args": [] + args: [], }, { - "name": "hasPreviousPage", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } + name: "hasPreviousPage", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, }, - "args": [] + args: [], }, { - "name": "startCursor", - "type": { - "kind": "SCALAR", - "name": "Any" + name: "startCursor", + type: { + kind: "SCALAR", + name: "Any", }, - "args": [] - } + args: [], + }, ], - "interfaces": [] + interfaces: [], }, { - "kind": "OBJECT", - "name": "Query", - "fields": [ + kind: "OBJECT", + name: "Query", + fields: [ { - "name": "browserSession", - "type": { - "kind": "OBJECT", - "name": "BrowserSession", - "ofType": null + name: "browserSession", + type: { + kind: "OBJECT", + name: "BrowserSession", + ofType: null, }, - "args": [ + args: [ { - "name": "id", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } - } - } - ] + name: "id", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, + }, + }, + ], }, { - "name": "currentBrowserSession", - "type": { - "kind": "OBJECT", - "name": "BrowserSession", - "ofType": null + name: "currentBrowserSession", + type: { + kind: "OBJECT", + name: "BrowserSession", + ofType: null, }, - "args": [] + args: [], }, { - "name": "currentUser", - "type": { - "kind": "OBJECT", - "name": "User", - "ofType": null + name: "currentUser", + type: { + kind: "OBJECT", + name: "User", + ofType: null, }, - "args": [] + args: [], }, { - "name": "node", - "type": { - "kind": "INTERFACE", - "name": "Node", - "ofType": null + name: "node", + type: { + kind: "INTERFACE", + name: "Node", + ofType: null, }, - "args": [ + args: [ { - "name": "id", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } - } - } - ] + name: "id", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, + }, + }, + ], }, { - "name": "oauth2Client", - "type": { - "kind": "OBJECT", - "name": "Oauth2Client", - "ofType": null + name: "oauth2Client", + type: { + kind: "OBJECT", + name: "Oauth2Client", + ofType: null, }, - "args": [ + args: [ { - "name": "id", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } - } - } - ] + name: "id", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, + }, + }, + ], }, { - "name": "upstreamOauth2Link", - "type": { - "kind": "OBJECT", - "name": "UpstreamOAuth2Link", - "ofType": null + name: "upstreamOauth2Link", + type: { + kind: "OBJECT", + name: "UpstreamOAuth2Link", + ofType: null, }, - "args": [ + args: [ { - "name": "id", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } - } - } - ] + name: "id", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, + }, + }, + ], }, { - "name": "upstreamOauth2Provider", - "type": { - "kind": "OBJECT", - "name": "UpstreamOAuth2Provider", - "ofType": null + name: "upstreamOauth2Provider", + type: { + kind: "OBJECT", + name: "UpstreamOAuth2Provider", + ofType: null, }, - "args": [ + args: [ { - "name": "id", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } - } - } - ] + name: "id", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, + }, + }, + ], }, { - "name": "upstreamOauth2Providers", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "OBJECT", - "name": "UpstreamOAuth2ProviderConnection", - "ofType": null - } + name: "upstreamOauth2Providers", + type: { + kind: "NON_NULL", + ofType: { + kind: "OBJECT", + name: "UpstreamOAuth2ProviderConnection", + ofType: null, + }, }, - "args": [ + args: [ { - "name": "after", - "type": { - "kind": "SCALAR", - "name": "Any" - } + name: "after", + type: { + kind: "SCALAR", + name: "Any", + }, }, { - "name": "before", - "type": { - "kind": "SCALAR", - "name": "Any" - } + name: "before", + type: { + kind: "SCALAR", + name: "Any", + }, }, { - "name": "first", - "type": { - "kind": "SCALAR", - "name": "Any" - } + name: "first", + type: { + kind: "SCALAR", + name: "Any", + }, }, { - "name": "last", - "type": { - "kind": "SCALAR", - "name": "Any" - } - } - ] + name: "last", + type: { + kind: "SCALAR", + name: "Any", + }, + }, + ], }, { - "name": "user", - "type": { - "kind": "OBJECT", - "name": "User", - "ofType": null + name: "user", + type: { + kind: "OBJECT", + name: "User", + ofType: null, }, - "args": [ + args: [ { - "name": "id", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } - } - } - ] + name: "id", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, + }, + }, + ], }, { - "name": "userEmail", - "type": { - "kind": "OBJECT", - "name": "UserEmail", - "ofType": null + name: "userEmail", + type: { + kind: "OBJECT", + name: "UserEmail", + ofType: null, }, - "args": [ + args: [ { - "name": "id", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } - } - } - ] + name: "id", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, + }, + }, + ], }, { - "name": "viewer", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "UNION", - "name": "Viewer", - "ofType": null - } + name: "viewer", + type: { + kind: "NON_NULL", + ofType: { + kind: "UNION", + name: "Viewer", + ofType: null, + }, }, - "args": [] + args: [], }, { - "name": "viewerSession", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "UNION", - "name": "ViewerSession", - "ofType": null - } + name: "viewerSession", + type: { + kind: "NON_NULL", + ofType: { + kind: "UNION", + name: "ViewerSession", + ofType: null, + }, }, - "args": [] - } + args: [], + }, ], - "interfaces": [] + interfaces: [], }, { - "kind": "OBJECT", - "name": "SendVerificationEmailPayload", - "fields": [ + kind: "OBJECT", + name: "RemoveEmailPayload", + fields: [ { - "name": "email", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "OBJECT", - "name": "UserEmail", - "ofType": null - } + name: "email", + type: { + kind: "OBJECT", + name: "UserEmail", + ofType: null, }, - "args": [] + args: [], }, { - "name": "status", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } + name: "status", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, }, - "args": [] + args: [], }, { - "name": "user", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "OBJECT", - "name": "User", - "ofType": null - } + name: "user", + type: { + kind: "OBJECT", + name: "User", + ofType: null, }, - "args": [] - } + args: [], + }, ], - "interfaces": [] + interfaces: [], }, { - "kind": "OBJECT", - "name": "UpstreamOAuth2Link", - "fields": [ + kind: "OBJECT", + name: "SendVerificationEmailPayload", + fields: [ { - "name": "createdAt", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } + name: "email", + type: { + kind: "NON_NULL", + ofType: { + kind: "OBJECT", + name: "UserEmail", + ofType: null, + }, }, - "args": [] + args: [], }, { - "name": "id", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } + name: "status", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, }, - "args": [] + args: [], }, { - "name": "provider", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "OBJECT", - "name": "UpstreamOAuth2Provider", - "ofType": null - } + name: "user", + type: { + kind: "NON_NULL", + ofType: { + kind: "OBJECT", + name: "User", + ofType: null, + }, }, - "args": [] + args: [], }, - { - "name": "subject", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } - }, - "args": [] - }, - { - "name": "user", - "type": { - "kind": "OBJECT", - "name": "User", - "ofType": null - }, - "args": [] - } ], - "interfaces": [ - { - "kind": "INTERFACE", - "name": "CreationEvent" - }, - { - "kind": "INTERFACE", - "name": "Node" - } - ] + interfaces: [], }, { - "kind": "OBJECT", - "name": "UpstreamOAuth2LinkConnection", - "fields": [ + kind: "OBJECT", + name: "UpstreamOAuth2Link", + fields: [ { - "name": "edges", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "LIST", - "ofType": { - "kind": "NON_NULL", - "ofType": { - "kind": "OBJECT", - "name": "UpstreamOAuth2LinkEdge", - "ofType": null - } - } - } + name: "createdAt", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, }, - "args": [] + args: [], }, { - "name": "nodes", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "LIST", - "ofType": { - "kind": "NON_NULL", - "ofType": { - "kind": "OBJECT", - "name": "UpstreamOAuth2Link", - "ofType": null - } - } - } + name: "id", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, }, - "args": [] + args: [], }, { - "name": "pageInfo", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "OBJECT", - "name": "PageInfo", - "ofType": null - } + name: "provider", + type: { + kind: "NON_NULL", + ofType: { + kind: "OBJECT", + name: "UpstreamOAuth2Provider", + ofType: null, + }, }, - "args": [] - } + args: [], + }, + { + name: "subject", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, + }, + args: [], + }, + { + name: "user", + type: { + kind: "OBJECT", + name: "User", + ofType: null, + }, + args: [], + }, ], - "interfaces": [] - }, - { - "kind": "OBJECT", - "name": "UpstreamOAuth2LinkEdge", - "fields": [ + interfaces: [ { - "name": "cursor", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } - }, - "args": [] + kind: "INTERFACE", + name: "CreationEvent", }, { - "name": "node", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "OBJECT", - "name": "UpstreamOAuth2Link", - "ofType": null - } - }, - "args": [] - } + kind: "INTERFACE", + name: "Node", + }, ], - "interfaces": [] }, { - "kind": "OBJECT", - "name": "UpstreamOAuth2Provider", - "fields": [ + kind: "OBJECT", + name: "UpstreamOAuth2LinkConnection", + fields: [ { - "name": "clientId", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } + name: "edges", + type: { + kind: "NON_NULL", + ofType: { + kind: "LIST", + ofType: { + kind: "NON_NULL", + ofType: { + kind: "OBJECT", + name: "UpstreamOAuth2LinkEdge", + ofType: null, + }, + }, + }, }, - "args": [] + args: [], }, { - "name": "createdAt", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } + name: "nodes", + type: { + kind: "NON_NULL", + ofType: { + kind: "LIST", + ofType: { + kind: "NON_NULL", + ofType: { + kind: "OBJECT", + name: "UpstreamOAuth2Link", + ofType: null, + }, + }, + }, }, - "args": [] + args: [], }, { - "name": "id", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } + name: "pageInfo", + type: { + kind: "NON_NULL", + ofType: { + kind: "OBJECT", + name: "PageInfo", + ofType: null, + }, }, - "args": [] + args: [], }, - { - "name": "issuer", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } - }, - "args": [] - } ], - "interfaces": [ - { - "kind": "INTERFACE", - "name": "CreationEvent" - }, - { - "kind": "INTERFACE", - "name": "Node" - } - ] + interfaces: [], }, { - "kind": "OBJECT", - "name": "UpstreamOAuth2ProviderConnection", - "fields": [ + kind: "OBJECT", + name: "UpstreamOAuth2LinkEdge", + fields: [ { - "name": "edges", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "LIST", - "ofType": { - "kind": "NON_NULL", - "ofType": { - "kind": "OBJECT", - "name": "UpstreamOAuth2ProviderEdge", - "ofType": null - } - } - } + name: "cursor", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, }, - "args": [] + args: [], }, { - "name": "nodes", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "LIST", - "ofType": { - "kind": "NON_NULL", - "ofType": { - "kind": "OBJECT", - "name": "UpstreamOAuth2Provider", - "ofType": null - } - } - } + name: "node", + type: { + kind: "NON_NULL", + ofType: { + kind: "OBJECT", + name: "UpstreamOAuth2Link", + ofType: null, + }, }, - "args": [] + args: [], }, - { - "name": "pageInfo", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "OBJECT", - "name": "PageInfo", - "ofType": null - } - }, - "args": [] - } ], - "interfaces": [] + interfaces: [], }, { - "kind": "OBJECT", - "name": "UpstreamOAuth2ProviderEdge", - "fields": [ + kind: "OBJECT", + name: "UpstreamOAuth2Provider", + fields: [ { - "name": "cursor", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } + name: "clientId", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, }, - "args": [] + args: [], }, { - "name": "node", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "OBJECT", - "name": "UpstreamOAuth2Provider", - "ofType": null - } + name: "createdAt", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, }, - "args": [] - } + args: [], + }, + { + name: "id", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, + }, + args: [], + }, + { + name: "issuer", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, + }, + args: [], + }, ], - "interfaces": [] - }, - { - "kind": "OBJECT", - "name": "User", - "fields": [ + interfaces: [ { - "name": "browserSessions", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "OBJECT", - "name": "BrowserSessionConnection", - "ofType": null - } - }, - "args": [ - { - "name": "after", - "type": { - "kind": "SCALAR", - "name": "Any" - } - }, - { - "name": "before", - "type": { - "kind": "SCALAR", - "name": "Any" - } - }, - { - "name": "first", - "type": { - "kind": "SCALAR", - "name": "Any" - } - }, - { - "name": "last", - "type": { - "kind": "SCALAR", - "name": "Any" - } - } - ] + kind: "INTERFACE", + name: "CreationEvent", }, { - "name": "compatSsoLogins", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "OBJECT", - "name": "CompatSsoLoginConnection", - "ofType": null - } - }, - "args": [ - { - "name": "after", - "type": { - "kind": "SCALAR", - "name": "Any" - } - }, - { - "name": "before", - "type": { - "kind": "SCALAR", - "name": "Any" - } - }, - { - "name": "first", - "type": { - "kind": "SCALAR", - "name": "Any" - } - }, - { - "name": "last", - "type": { - "kind": "SCALAR", - "name": "Any" - } - } - ] + kind: "INTERFACE", + name: "Node", }, - { - "name": "emails", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "OBJECT", - "name": "UserEmailConnection", - "ofType": null - } - }, - "args": [ - { - "name": "after", - "type": { - "kind": "SCALAR", - "name": "Any" - } - }, - { - "name": "before", - "type": { - "kind": "SCALAR", - "name": "Any" - } - }, - { - "name": "first", - "type": { - "kind": "SCALAR", - "name": "Any" - } - }, - { - "name": "last", - "type": { - "kind": "SCALAR", - "name": "Any" - } - } - ] - }, - { - "name": "id", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } - }, - "args": [] - }, - { - "name": "oauth2Sessions", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "OBJECT", - "name": "Oauth2SessionConnection", - "ofType": null - } - }, - "args": [ - { - "name": "after", - "type": { - "kind": "SCALAR", - "name": "Any" - } - }, - { - "name": "before", - "type": { - "kind": "SCALAR", - "name": "Any" - } - }, - { - "name": "first", - "type": { - "kind": "SCALAR", - "name": "Any" - } - }, - { - "name": "last", - "type": { - "kind": "SCALAR", - "name": "Any" - } - } - ] - }, - { - "name": "primaryEmail", - "type": { - "kind": "OBJECT", - "name": "UserEmail", - "ofType": null - }, - "args": [] - }, - { - "name": "upstreamOauth2Links", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "OBJECT", - "name": "UpstreamOAuth2LinkConnection", - "ofType": null - } - }, - "args": [ - { - "name": "after", - "type": { - "kind": "SCALAR", - "name": "Any" - } - }, - { - "name": "before", - "type": { - "kind": "SCALAR", - "name": "Any" - } - }, - { - "name": "first", - "type": { - "kind": "SCALAR", - "name": "Any" - } - }, - { - "name": "last", - "type": { - "kind": "SCALAR", - "name": "Any" - } - } - ] - }, - { - "name": "username", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } - }, - "args": [] - } ], - "interfaces": [ - { - "kind": "INTERFACE", - "name": "Node" - } - ] }, { - "kind": "OBJECT", - "name": "UserEmail", - "fields": [ + kind: "OBJECT", + name: "UpstreamOAuth2ProviderConnection", + fields: [ { - "name": "confirmedAt", - "type": { - "kind": "SCALAR", - "name": "Any" + name: "edges", + type: { + kind: "NON_NULL", + ofType: { + kind: "LIST", + ofType: { + kind: "NON_NULL", + ofType: { + kind: "OBJECT", + name: "UpstreamOAuth2ProviderEdge", + ofType: null, + }, + }, + }, }, - "args": [] + args: [], }, { - "name": "createdAt", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } + name: "nodes", + type: { + kind: "NON_NULL", + ofType: { + kind: "LIST", + ofType: { + kind: "NON_NULL", + ofType: { + kind: "OBJECT", + name: "UpstreamOAuth2Provider", + ofType: null, + }, + }, + }, }, - "args": [] + args: [], }, { - "name": "email", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } + name: "pageInfo", + type: { + kind: "NON_NULL", + ofType: { + kind: "OBJECT", + name: "PageInfo", + ofType: null, + }, }, - "args": [] + args: [], }, - { - "name": "id", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } - }, - "args": [] - } ], - "interfaces": [ - { - "kind": "INTERFACE", - "name": "CreationEvent" - }, - { - "kind": "INTERFACE", - "name": "Node" - } - ] + interfaces: [], }, { - "kind": "OBJECT", - "name": "UserEmailConnection", - "fields": [ + kind: "OBJECT", + name: "UpstreamOAuth2ProviderEdge", + fields: [ { - "name": "edges", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "LIST", - "ofType": { - "kind": "NON_NULL", - "ofType": { - "kind": "OBJECT", - "name": "UserEmailEdge", - "ofType": null - } - } - } + name: "cursor", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, }, - "args": [] + args: [], }, { - "name": "nodes", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "LIST", - "ofType": { - "kind": "NON_NULL", - "ofType": { - "kind": "OBJECT", - "name": "UserEmail", - "ofType": null - } - } - } + name: "node", + type: { + kind: "NON_NULL", + ofType: { + kind: "OBJECT", + name: "UpstreamOAuth2Provider", + ofType: null, + }, }, - "args": [] + args: [], }, - { - "name": "pageInfo", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "OBJECT", - "name": "PageInfo", - "ofType": null - } - }, - "args": [] - }, - { - "name": "totalCount", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } - }, - "args": [] - } ], - "interfaces": [] + interfaces: [], }, { - "kind": "OBJECT", - "name": "UserEmailEdge", - "fields": [ + kind: "OBJECT", + name: "User", + fields: [ { - "name": "cursor", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } + name: "browserSessions", + type: { + kind: "NON_NULL", + ofType: { + kind: "OBJECT", + name: "BrowserSessionConnection", + ofType: null, + }, }, - "args": [] + args: [ + { + name: "after", + type: { + kind: "SCALAR", + name: "Any", + }, + }, + { + name: "before", + type: { + kind: "SCALAR", + name: "Any", + }, + }, + { + name: "first", + type: { + kind: "SCALAR", + name: "Any", + }, + }, + { + name: "last", + type: { + kind: "SCALAR", + name: "Any", + }, + }, + ], }, { - "name": "node", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "OBJECT", - "name": "UserEmail", - "ofType": null - } + name: "compatSsoLogins", + type: { + kind: "NON_NULL", + ofType: { + kind: "OBJECT", + name: "CompatSsoLoginConnection", + ofType: null, + }, }, - "args": [] - } + args: [ + { + name: "after", + type: { + kind: "SCALAR", + name: "Any", + }, + }, + { + name: "before", + type: { + kind: "SCALAR", + name: "Any", + }, + }, + { + name: "first", + type: { + kind: "SCALAR", + name: "Any", + }, + }, + { + name: "last", + type: { + kind: "SCALAR", + name: "Any", + }, + }, + ], + }, + { + name: "emails", + type: { + kind: "NON_NULL", + ofType: { + kind: "OBJECT", + name: "UserEmailConnection", + ofType: null, + }, + }, + args: [ + { + name: "after", + type: { + kind: "SCALAR", + name: "Any", + }, + }, + { + name: "before", + type: { + kind: "SCALAR", + name: "Any", + }, + }, + { + name: "first", + type: { + kind: "SCALAR", + name: "Any", + }, + }, + { + name: "last", + type: { + kind: "SCALAR", + name: "Any", + }, + }, + ], + }, + { + name: "id", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, + }, + args: [], + }, + { + name: "oauth2Sessions", + type: { + kind: "NON_NULL", + ofType: { + kind: "OBJECT", + name: "Oauth2SessionConnection", + ofType: null, + }, + }, + args: [ + { + name: "after", + type: { + kind: "SCALAR", + name: "Any", + }, + }, + { + name: "before", + type: { + kind: "SCALAR", + name: "Any", + }, + }, + { + name: "first", + type: { + kind: "SCALAR", + name: "Any", + }, + }, + { + name: "last", + type: { + kind: "SCALAR", + name: "Any", + }, + }, + ], + }, + { + name: "primaryEmail", + type: { + kind: "OBJECT", + name: "UserEmail", + ofType: null, + }, + args: [], + }, + { + name: "upstreamOauth2Links", + type: { + kind: "NON_NULL", + ofType: { + kind: "OBJECT", + name: "UpstreamOAuth2LinkConnection", + ofType: null, + }, + }, + args: [ + { + name: "after", + type: { + kind: "SCALAR", + name: "Any", + }, + }, + { + name: "before", + type: { + kind: "SCALAR", + name: "Any", + }, + }, + { + name: "first", + type: { + kind: "SCALAR", + name: "Any", + }, + }, + { + name: "last", + type: { + kind: "SCALAR", + name: "Any", + }, + }, + ], + }, + { + name: "username", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, + }, + args: [], + }, ], - "interfaces": [] - }, - { - "kind": "OBJECT", - "name": "VerifyEmailPayload", - "fields": [ + interfaces: [ { - "name": "email", - "type": { - "kind": "OBJECT", - "name": "UserEmail", - "ofType": null - }, - "args": [] + kind: "INTERFACE", + name: "Node", }, - { - "name": "status", - "type": { - "kind": "NON_NULL", - "ofType": { - "kind": "SCALAR", - "name": "Any" - } - }, - "args": [] - }, - { - "name": "user", - "type": { - "kind": "OBJECT", - "name": "User", - "ofType": null - }, - "args": [] - } ], - "interfaces": [] }, { - "kind": "UNION", - "name": "Viewer", - "possibleTypes": [ + kind: "OBJECT", + name: "UserEmail", + fields: [ { - "kind": "OBJECT", - "name": "Anonymous" + name: "confirmedAt", + type: { + kind: "SCALAR", + name: "Any", + }, + args: [], }, { - "kind": "OBJECT", - "name": "User" - } - ] - }, - { - "kind": "UNION", - "name": "ViewerSession", - "possibleTypes": [ - { - "kind": "OBJECT", - "name": "Anonymous" + name: "createdAt", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, + }, + args: [], }, { - "kind": "OBJECT", - "name": "BrowserSession" - } - ] + name: "email", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, + }, + args: [], + }, + { + name: "id", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, + }, + args: [], + }, + ], + interfaces: [ + { + kind: "INTERFACE", + name: "CreationEvent", + }, + { + kind: "INTERFACE", + name: "Node", + }, + ], }, { - "kind": "SCALAR", - "name": "Any" - } + kind: "OBJECT", + name: "UserEmailConnection", + fields: [ + { + name: "edges", + type: { + kind: "NON_NULL", + ofType: { + kind: "LIST", + ofType: { + kind: "NON_NULL", + ofType: { + kind: "OBJECT", + name: "UserEmailEdge", + ofType: null, + }, + }, + }, + }, + args: [], + }, + { + name: "nodes", + type: { + kind: "NON_NULL", + ofType: { + kind: "LIST", + ofType: { + kind: "NON_NULL", + ofType: { + kind: "OBJECT", + name: "UserEmail", + ofType: null, + }, + }, + }, + }, + args: [], + }, + { + name: "pageInfo", + type: { + kind: "NON_NULL", + ofType: { + kind: "OBJECT", + name: "PageInfo", + ofType: null, + }, + }, + args: [], + }, + { + name: "totalCount", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, + }, + args: [], + }, + ], + interfaces: [], + }, + { + kind: "OBJECT", + name: "UserEmailEdge", + fields: [ + { + name: "cursor", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, + }, + args: [], + }, + { + name: "node", + type: { + kind: "NON_NULL", + ofType: { + kind: "OBJECT", + name: "UserEmail", + ofType: null, + }, + }, + args: [], + }, + ], + interfaces: [], + }, + { + kind: "OBJECT", + name: "VerifyEmailPayload", + fields: [ + { + name: "email", + type: { + kind: "OBJECT", + name: "UserEmail", + ofType: null, + }, + args: [], + }, + { + name: "status", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, + }, + args: [], + }, + { + name: "user", + type: { + kind: "OBJECT", + name: "User", + ofType: null, + }, + args: [], + }, + ], + interfaces: [], + }, + { + kind: "UNION", + name: "Viewer", + possibleTypes: [ + { + kind: "OBJECT", + name: "Anonymous", + }, + { + kind: "OBJECT", + name: "User", + }, + ], + }, + { + kind: "UNION", + name: "ViewerSession", + possibleTypes: [ + { + kind: "OBJECT", + name: "Anonymous", + }, + { + kind: "OBJECT", + name: "BrowserSession", + }, + ], + }, + { + kind: "SCALAR", + name: "Any", + }, ], - "directives": [] - } -} as unknown as IntrospectionQuery; \ No newline at end of file + directives: [], + }, +} as unknown as IntrospectionQuery; diff --git a/frontend/src/graphql.ts b/frontend/src/graphql.ts index 00024013..550afdae 100644 --- a/frontend/src/graphql.ts +++ b/frontend/src/graphql.ts @@ -18,7 +18,11 @@ import { cacheExchange } from "@urql/exchange-graphcache"; import { refocusExchange } from "@urql/exchange-refocus"; import { requestPolicyExchange } from "@urql/exchange-request-policy"; -import type { MutationAddEmailArgs } from "./gql/graphql"; +import type { + MutationAddEmailArgs, + MutationRemoveEmailArgs, + RemoveEmailPayload, +} from "./gql/graphql"; import schema from "./gql/schema"; const cache = cacheExchange({ @@ -39,6 +43,36 @@ const cache = cacheExchange({ cache.invalidate(key, field.fieldName, field.arguments); }); }, + + removeEmail: ( + result: { removeEmail?: RemoveEmailPayload }, + args: MutationRemoveEmailArgs, + cache, + _info + ) => { + // Invalidate the email entity + cache.invalidate({ + __typename: "UserEmail", + id: args.input.userEmailId, + }); + + // Let's try to figure out the userId to invalidate the emails field on the User object + const userId = result.removeEmail?.user?.id; + if (userId) { + const key = cache.keyOfEntity({ + __typename: "User", + id: userId, + }); + + // Invalidate the emails field on the User object so that it gets refetched + cache + .inspectFields(key) + .filter((field) => field.fieldName === "emails") + .forEach((field) => { + cache.invalidate(key, field.fieldName, field.arguments); + }); + } + }, }, }, });