diff --git a/crates/graphql/src/lib.rs b/crates/graphql/src/lib.rs index 162e1d0f..5f0201b8 100644 --- a/crates/graphql/src/lib.rs +++ b/crates/graphql/src/lib.rs @@ -36,17 +36,17 @@ mod state; pub use self::{ model::{CreationEvent, Node}, - mutations::RootMutations, - query::RootQuery, + mutations::Mutation, + query::Query, state::{BoxState, State}, }; -pub type Schema = async_graphql::Schema; -pub type SchemaBuilder = async_graphql::SchemaBuilder; +pub type Schema = async_graphql::Schema; +pub type SchemaBuilder = async_graphql::SchemaBuilder; #[must_use] pub fn schema_builder() -> SchemaBuilder { - async_graphql::Schema::build(RootQuery::new(), RootMutations::new(), EmptySubscription) + async_graphql::Schema::build(Query::new(), Mutation::new(), EmptySubscription) .register_output_type::() .register_output_type::() } diff --git a/crates/graphql/src/mutations/mod.rs b/crates/graphql/src/mutations/mod.rs index 5d2676b0..bbcbd992 100644 --- a/crates/graphql/src/mutations/mod.rs +++ b/crates/graphql/src/mutations/mod.rs @@ -18,9 +18,9 @@ use async_graphql::MergedObject; /// The mutations root of the GraphQL interface. #[derive(Default, MergedObject)] -pub struct RootMutations(user_email::UserEmailMutations); +pub struct Mutation(user_email::UserEmailMutations); -impl RootMutations { +impl Mutation { #[must_use] pub fn new() -> Self { Self::default() diff --git a/crates/graphql/src/mutations/user_email.rs b/crates/graphql/src/mutations/user_email.rs index 5034107c..c48e5b6f 100644 --- a/crates/graphql/src/mutations/user_email.rs +++ b/crates/graphql/src/mutations/user_email.rs @@ -13,11 +13,15 @@ // limitations under the License. use anyhow::Context as _; -use async_graphql::{Context, InputObject, Object, ID}; -use mas_storage::job::{JobRepositoryExt, ProvisionUserJob, VerifyEmailJob}; +use async_graphql::{Context, Description, Enum, InputObject, Object, ID}; +use mas_storage::{ + job::{JobRepositoryExt, ProvisionUserJob, VerifyEmailJob}, + user::UserRepository, + RepositoryAccess, +}; use crate::{ - model::{NodeType, UserEmail}, + model::{NodeType, User, UserEmail}, state::ContextExt, }; @@ -35,6 +39,60 @@ struct AddEmailInput { user_id: ID, } +/// The status of the `addEmail` mutation +#[derive(Enum, Copy, Clone, Eq, PartialEq)] +pub enum AddEmailStatus { + /// The email address was added + Added, + /// The email address already exists + Exists, +} + +/// The payload of the `addEmail` mutation +#[derive(Description)] +enum AddEmailPayload { + Added(mas_data_model::UserEmail), + Exists(mas_data_model::UserEmail), +} + +#[Object(use_type_description)] +impl AddEmailPayload { + /// Status of the operation + async fn status(&self) -> AddEmailStatus { + match self { + AddEmailPayload::Added(_) => AddEmailStatus::Added, + AddEmailPayload::Exists(_) => AddEmailStatus::Exists, + } + } + + /// The email address that was added + async fn email(&self) -> UserEmail { + match self { + AddEmailPayload::Added(email) | AddEmailPayload::Exists(email) => { + UserEmail(email.clone()) + } + } + } + + /// The user to whom the email address was added + async fn user(&self, ctx: &Context<'_>) -> Result { + let state = ctx.state(); + let mut repo = state.repository().await?; + + let user_id = match self { + AddEmailPayload::Added(email) | AddEmailPayload::Exists(email) => email.user_id, + }; + + let user = repo + .user() + .lookup(user_id) + .await? + .context("User not found")?; + + Ok(User(user)) + } +} + /// The input for the `sendVerificationEmail` mutation #[derive(InputObject)] struct SendVerificationEmailInput { @@ -42,6 +100,62 @@ struct SendVerificationEmailInput { user_email_id: ID, } +/// The status of the `sendVerificationEmail` mutation +#[derive(Enum, Copy, Clone, Eq, PartialEq)] +enum SendVerificationEmailStatus { + /// The verification email was sent + Sent, + /// The email address is already verified + AlreadyVerified, +} + +/// The payload of the `sendVerificationEmail` mutation +#[derive(Description)] +enum SendVerificationEmailPayload { + Sent(mas_data_model::UserEmail), + AlreadyVerified(mas_data_model::UserEmail), +} + +#[Object(use_type_description)] +impl SendVerificationEmailPayload { + /// Status of the operation + async fn status(&self) -> SendVerificationEmailStatus { + match self { + SendVerificationEmailPayload::Sent(_) => SendVerificationEmailStatus::Sent, + SendVerificationEmailPayload::AlreadyVerified(_) => { + SendVerificationEmailStatus::AlreadyVerified + } + } + } + + /// The email address to which the verification email was sent + async fn email(&self) -> UserEmail { + match self { + SendVerificationEmailPayload::Sent(email) + | SendVerificationEmailPayload::AlreadyVerified(email) => UserEmail(email.clone()), + } + } + + /// The user to whom the email address belongs + async fn user(&self, ctx: &Context<'_>) -> Result { + let state = ctx.state(); + let mut repo = state.repository().await?; + + let user_id = match self { + SendVerificationEmailPayload::Sent(email) + | SendVerificationEmailPayload::AlreadyVerified(email) => email.user_id, + }; + + let user = repo + .user() + .lookup(user_id) + .await? + .context("User not found")?; + + Ok(User(user)) + } +} + /// The input for the `verifyEmail` mutation #[derive(InputObject)] struct VerifyEmailInput { @@ -51,6 +165,68 @@ struct VerifyEmailInput { code: String, } +/// The status of the `verifyEmail` mutation +#[derive(Enum, Copy, Clone, Eq, PartialEq)] +enum VerifyEmailStatus { + /// The email address was just verified + Verified, + /// The email address was already verified before + AlreadyVerified, + /// The verification code is invalid + InvalidCode, +} + +/// The payload of the `verifyEmail` mutation +#[derive(Description)] +enum VerifyEmailPayload { + Verified(mas_data_model::UserEmail), + AlreadyVerified(mas_data_model::UserEmail), + InvalidCode, +} + +#[Object(use_type_description)] +impl VerifyEmailPayload { + /// Status of the operation + async fn status(&self) -> VerifyEmailStatus { + match self { + VerifyEmailPayload::Verified(_) => VerifyEmailStatus::Verified, + VerifyEmailPayload::AlreadyVerified(_) => VerifyEmailStatus::AlreadyVerified, + VerifyEmailPayload::InvalidCode => VerifyEmailStatus::InvalidCode, + } + } + + /// The email address that was verified + async fn email(&self) -> Option { + match self { + VerifyEmailPayload::Verified(email) | VerifyEmailPayload::AlreadyVerified(email) => { + Some(UserEmail(email.clone())) + } + VerifyEmailPayload::InvalidCode => None, + } + } + + /// The user to whom the email address belongs + 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 { + VerifyEmailPayload::Verified(email) | VerifyEmailPayload::AlreadyVerified(email) => { + email.user_id + } + VerifyEmailPayload::InvalidCode => 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 @@ -58,7 +234,7 @@ impl UserEmailMutations { &self, ctx: &Context<'_>, input: AddEmailInput, - ) -> Result { + ) -> Result { let state = ctx.state(); let id = NodeType::User.extract_ulid(&input.user_id)?; let requester = ctx.requester(); @@ -75,15 +251,18 @@ impl UserEmailMutations { // duplicated in mas_handlers // Find an existing email address let existing_user_email = repo.user_email().find(user, &input.email).await?; - let user_email = if let Some(user_email) = existing_user_email { - user_email + let (added, user_email) = if let Some(user_email) = existing_user_email { + (false, user_email) } else { let clock = state.clock(); let mut rng = state.rng(); - repo.user_email() + let user_email = repo + .user_email() .add(&mut rng, &clock, user, input.email) - .await? + .await?; + + (true, user_email) }; // Schedule a job to verify the email address if needed @@ -95,7 +274,12 @@ impl UserEmailMutations { repo.save().await?; - Ok(UserEmail(user_email)) + let payload = if added { + AddEmailPayload::Added(user_email) + } else { + AddEmailPayload::Exists(user_email) + }; + Ok(payload) } /// Send a verification code for an email address @@ -103,7 +287,7 @@ impl UserEmailMutations { &self, ctx: &Context<'_>, input: SendVerificationEmailInput, - ) -> Result { + ) -> Result { let state = ctx.state(); let user_email_id = NodeType::UserEmail.extract_ulid(&input.user_email_id)?; let requester = ctx.requester(); @@ -122,7 +306,8 @@ impl UserEmailMutations { } // Schedule a job to verify the email address if needed - if user_email.confirmed_at.is_none() { + let needs_verification = user_email.confirmed_at.is_none(); + if needs_verification { repo.job() .schedule_job(VerifyEmailJob::new(&user_email)) .await?; @@ -130,7 +315,12 @@ impl UserEmailMutations { repo.save().await?; - Ok(UserEmail(user_email)) + let payload = if needs_verification { + SendVerificationEmailPayload::Sent(user_email) + } else { + SendVerificationEmailPayload::AlreadyVerified(user_email) + }; + Ok(payload) } /// Submit a verification code for an email address @@ -138,7 +328,7 @@ impl UserEmailMutations { &self, ctx: &Context<'_>, input: VerifyEmailInput, - ) -> Result { + ) -> Result { let state = ctx.state(); let user_email_id = NodeType::UserEmail.extract_ulid(&input.user_email_id)?; let requester = ctx.requester(); @@ -161,7 +351,7 @@ impl UserEmailMutations { if user_email.confirmed_at.is_some() { // Just return the email address if it's already verified // XXX: should we return an error instead? - return Ok(UserEmail(user_email)); + return Ok(VerifyEmailPayload::AlreadyVerified(user_email)); } // XXX: this logic should be extracted somewhere else, since most of it is @@ -172,13 +362,12 @@ impl UserEmailMutations { .user_email() .find_verification_code(&clock, &user_email, &input.code) .await? - .context("Invalid verification code")?; + .filter(|v| v.is_valid()); - if verification.is_valid() { - return Err(async_graphql::Error::new("Invalid verification code")); - } + let Some(verification) = verification else { + return Ok(VerifyEmailPayload::InvalidCode); + }; - // TODO: display nice errors if the code was already consumed or expired repo.user_email() .consume_verification_code(&clock, verification) .await?; @@ -197,6 +386,6 @@ impl UserEmailMutations { repo.save().await?; - Ok(UserEmail(user_email)) + Ok(VerifyEmailPayload::Verified(user_email)) } } diff --git a/crates/graphql/src/query/mod.rs b/crates/graphql/src/query/mod.rs index 6c379fa0..a58f9454 100644 --- a/crates/graphql/src/query/mod.rs +++ b/crates/graphql/src/query/mod.rs @@ -26,9 +26,9 @@ use self::{upstream_oauth::UpstreamOAuthQuery, viewer::ViewerQuery}; /// The query root of the GraphQL interface. #[derive(Default, MergedObject)] -pub struct RootQuery(BaseQuery, UpstreamOAuthQuery, ViewerQuery); +pub struct Query(BaseQuery, UpstreamOAuthQuery, ViewerQuery); -impl RootQuery { +impl Query { #[must_use] pub fn new() -> Self { Self::default() diff --git a/frontend/.eslintrc.cjs b/frontend/.eslintrc.cjs index 0a3cef7b..524119d0 100644 --- a/frontend/.eslintrc.cjs +++ b/frontend/.eslintrc.cjs @@ -51,6 +51,10 @@ module.exports = { "plugin:prettier/recommended", ], rules: { + "@graphql-eslint/input-name": [ + "error", + { checkInputType: true, caseSensitiveInputType: false }, + ], "@graphql-eslint/relay-edge-types": [ "error", { @@ -64,9 +68,9 @@ module.exports = { "error", { exceptions: { - // The '*Connection', '*Edge' and 'PageInfo' types don't have IDs + // The '*Connection', '*Edge', '*Payload' and 'PageInfo' types don't have IDs types: ["PageInfo"], - suffixes: ["Connection", "Edge"], + suffixes: ["Connection", "Edge", "Payload"], }, }, ], diff --git a/frontend/schema.graphql b/frontend/schema.graphql index 2fb47373..dac6df89 100644 --- a/frontend/schema.graphql +++ b/frontend/schema.graphql @@ -12,6 +12,38 @@ input AddEmailInput { userId: ID! } +""" +The payload of the `addEmail` mutation +""" +type AddEmailPayload { + """ + Status of the operation + """ + status: AddEmailStatus! + """ + The email address that was added + """ + email: UserEmail! + """ + The user to whom the email address was added + """ + user: User! +} + +""" +The status of the `addEmail` mutation +""" +enum AddEmailStatus { + """ + The email address was added + """ + ADDED + """ + The email address already exists + """ + EXISTS +} + type Anonymous implements Node { id: ID! } @@ -187,6 +219,26 @@ The input/output is a string in RFC3339 format. """ scalar DateTime +""" +The mutations root of the GraphQL interface. +""" +type Mutation { + """ + Add an email address to the specified user + """ + addEmail(input: AddEmailInput!): AddEmailPayload! + """ + Send a verification code for an email address + """ + sendVerificationEmail( + input: SendVerificationEmailInput! + ): SendVerificationEmailPayload! + """ + Submit a verification code for an email address + """ + verifyEmail(input: VerifyEmailInput!): VerifyEmailPayload! +} + """ An object with an ID. """ @@ -309,28 +361,10 @@ type PageInfo { endCursor: String } -""" -The mutations root of the GraphQL interface. -""" -type RootMutations { - """ - Add an email address to the specified user - """ - addEmail(input: AddEmailInput!): UserEmail! - """ - Send a verification code for an email address - """ - sendVerificationEmail(input: SendVerificationEmailInput!): UserEmail! - """ - Submit a verification code for an email address - """ - verifyEmail(input: VerifyEmailInput!): UserEmail! -} - """ The query root of the GraphQL interface. """ -type RootQuery { +type Query { """ Get the current logged in browser session """ @@ -397,6 +431,38 @@ input SendVerificationEmailInput { userEmailId: ID! } +""" +The payload of the `sendVerificationEmail` mutation +""" +type SendVerificationEmailPayload { + """ + Status of the operation + """ + status: SendVerificationEmailStatus! + """ + The email address to which the verification email was sent + """ + email: UserEmail! + """ + The user to whom the email address belongs + """ + user: User! +} + +""" +The status of the `sendVerificationEmail` mutation +""" +enum SendVerificationEmailStatus { + """ + The verification email was sent + """ + SENT + """ + The email address is already verified + """ + ALREADY_VERIFIED +} + type UpstreamOAuth2Link implements Node & CreationEvent { """ ID of the object. @@ -635,6 +701,42 @@ input VerifyEmailInput { code: String! } +""" +The payload of the `verifyEmail` mutation +""" +type VerifyEmailPayload { + """ + Status of the operation + """ + status: VerifyEmailStatus! + """ + The email address that was verified + """ + email: UserEmail + """ + The user to whom the email address belongs + """ + user: User +} + +""" +The status of the `verifyEmail` mutation +""" +enum VerifyEmailStatus { + """ + The email address was just verified + """ + VERIFIED + """ + The email address was already verified before + """ + ALREADY_VERIFIED + """ + The verification code is invalid + """ + INVALID_CODE +} + """ Represents the current viewer """ @@ -646,6 +748,6 @@ Represents the current viewer's session union ViewerSession = BrowserSession | Anonymous schema { - query: RootQuery - mutation: RootMutations + query: Query + mutation: Mutation } diff --git a/frontend/src/gql/graphql.ts b/frontend/src/gql/graphql.ts index 8bcdbeba..7cec3f71 100644 --- a/frontend/src/gql/graphql.ts +++ b/frontend/src/gql/graphql.ts @@ -30,6 +30,25 @@ export type AddEmailInput = { userId: Scalars['ID']; }; +/** The payload of the `addEmail` mutation */ +export type AddEmailPayload = { + __typename?: 'AddEmailPayload'; + /** The email address that was added */ + email: UserEmail; + /** Status of the operation */ + status: AddEmailStatus; + /** The user to whom the email address was added */ + user: User; +}; + +/** The status of the `addEmail` mutation */ +export enum AddEmailStatus { + /** The email address was added */ + Added = 'ADDED', + /** The email address already exists */ + Exists = 'EXISTS' +} + export type Anonymous = Node & { __typename?: 'Anonymous'; id: Scalars['ID']; @@ -145,6 +164,35 @@ export type CreationEvent = { createdAt: Scalars['DateTime']; }; +/** The mutations root of the GraphQL interface. */ +export type Mutation = { + __typename?: 'Mutation'; + /** Add an email address to the specified user */ + addEmail: AddEmailPayload; + /** Send a verification code for an email address */ + sendVerificationEmail: SendVerificationEmailPayload; + /** Submit a verification code for an email address */ + verifyEmail: VerifyEmailPayload; +}; + + +/** The mutations root of the GraphQL interface. */ +export type MutationAddEmailArgs = { + input: AddEmailInput; +}; + + +/** The mutations root of the GraphQL interface. */ +export type MutationSendVerificationEmailArgs = { + input: SendVerificationEmailInput; +}; + + +/** The mutations root of the GraphQL interface. */ +export type MutationVerifyEmailArgs = { + input: VerifyEmailInput; +}; + /** An object with an ID. */ export type Node = { /** ID of the object. */ @@ -220,38 +268,9 @@ export type PageInfo = { startCursor?: Maybe; }; -/** The mutations root of the GraphQL interface. */ -export type RootMutations = { - __typename?: 'RootMutations'; - /** Add an email address to the specified user */ - addEmail: UserEmail; - /** Send a verification code for an email address */ - sendVerificationEmail: UserEmail; - /** Submit a verification code for an email address */ - verifyEmail: UserEmail; -}; - - -/** The mutations root of the GraphQL interface. */ -export type RootMutationsAddEmailArgs = { - input: AddEmailInput; -}; - - -/** The mutations root of the GraphQL interface. */ -export type RootMutationsSendVerificationEmailArgs = { - input: SendVerificationEmailInput; -}; - - -/** The mutations root of the GraphQL interface. */ -export type RootMutationsVerifyEmailArgs = { - input: VerifyEmailInput; -}; - /** The query root of the GraphQL interface. */ -export type RootQuery = { - __typename?: 'RootQuery'; +export type Query = { + __typename?: 'Query'; /** Fetch a browser session by its ID. */ browserSession?: Maybe; /** @@ -286,37 +305,37 @@ export type RootQuery = { /** The query root of the GraphQL interface. */ -export type RootQueryBrowserSessionArgs = { +export type QueryBrowserSessionArgs = { id: Scalars['ID']; }; /** The query root of the GraphQL interface. */ -export type RootQueryNodeArgs = { +export type QueryNodeArgs = { id: Scalars['ID']; }; /** The query root of the GraphQL interface. */ -export type RootQueryOauth2ClientArgs = { +export type QueryOauth2ClientArgs = { id: Scalars['ID']; }; /** The query root of the GraphQL interface. */ -export type RootQueryUpstreamOauth2LinkArgs = { +export type QueryUpstreamOauth2LinkArgs = { id: Scalars['ID']; }; /** The query root of the GraphQL interface. */ -export type RootQueryUpstreamOauth2ProviderArgs = { +export type QueryUpstreamOauth2ProviderArgs = { id: Scalars['ID']; }; /** The query root of the GraphQL interface. */ -export type RootQueryUpstreamOauth2ProvidersArgs = { +export type QueryUpstreamOauth2ProvidersArgs = { after?: InputMaybe; before?: InputMaybe; first?: InputMaybe; @@ -325,13 +344,13 @@ export type RootQueryUpstreamOauth2ProvidersArgs = { /** The query root of the GraphQL interface. */ -export type RootQueryUserArgs = { +export type QueryUserArgs = { id: Scalars['ID']; }; /** The query root of the GraphQL interface. */ -export type RootQueryUserEmailArgs = { +export type QueryUserEmailArgs = { id: Scalars['ID']; }; @@ -341,6 +360,25 @@ export type SendVerificationEmailInput = { userEmailId: Scalars['ID']; }; +/** The payload of the `sendVerificationEmail` mutation */ +export type SendVerificationEmailPayload = { + __typename?: 'SendVerificationEmailPayload'; + /** The email address to which the verification email was sent */ + email: UserEmail; + /** Status of the operation */ + status: SendVerificationEmailStatus; + /** The user to whom the email address belongs */ + user: User; +}; + +/** The status of the `sendVerificationEmail` mutation */ +export enum SendVerificationEmailStatus { + /** The email address is already verified */ + AlreadyVerified = 'ALREADY_VERIFIED', + /** The verification email was sent */ + Sent = 'SENT' +} + export type UpstreamOAuth2Link = CreationEvent & Node & { __typename?: 'UpstreamOAuth2Link'; /** When the object was created. */ @@ -516,6 +554,27 @@ export type VerifyEmailInput = { userEmailId: Scalars['ID']; }; +/** The payload of the `verifyEmail` mutation */ +export type VerifyEmailPayload = { + __typename?: 'VerifyEmailPayload'; + /** The email address that was verified */ + email?: Maybe; + /** Status of the operation */ + status: VerifyEmailStatus; + /** The user to whom the email address belongs */ + user?: Maybe; +}; + +/** The status of the `verifyEmail` mutation */ +export enum VerifyEmailStatus { + /** The email address was already verified before */ + AlreadyVerified = 'ALREADY_VERIFIED', + /** The verification code is invalid */ + InvalidCode = 'INVALID_CODE', + /** The email address was just verified */ + Verified = 'VERIFIED' +} + /** Represents the current viewer */ export type Viewer = Anonymous | User; @@ -548,7 +607,7 @@ export type BrowserSessionQueryQueryVariables = Exact<{ }>; -export type BrowserSessionQueryQuery = { __typename?: 'RootQuery', browserSession?: { __typename?: 'BrowserSession', id: string, createdAt: any, lastAuthentication?: { __typename?: 'Authentication', id: string, createdAt: any } | null, user: { __typename?: 'User', id: string, username: string } } | null }; +export type BrowserSessionQueryQuery = { __typename?: 'Query', browserSession?: { __typename?: 'BrowserSession', id: string, createdAt: any, lastAuthentication?: { __typename?: 'Authentication', id: string, createdAt: any } | null, user: { __typename?: 'User', id: string, username: string } } | null }; export type HomeQueryQueryVariables = Exact<{ count: Scalars['Int']; @@ -556,7 +615,7 @@ export type HomeQueryQueryVariables = Exact<{ }>; -export type HomeQueryQuery = { __typename?: 'RootQuery', currentBrowserSession?: { __typename?: 'BrowserSession', id: string, user: ( +export type HomeQueryQuery = { __typename?: 'Query', currentBrowserSession?: { __typename?: 'BrowserSession', id: string, user: ( { __typename?: 'User', id: string, username: string } & { ' $fragmentRefs'?: { 'CompatSsoLoginList_UserFragment': CompatSsoLoginList_UserFragment;'BrowserSessionList_UserFragment': BrowserSessionList_UserFragment;'OAuth2SessionList_UserFragment': OAuth2SessionList_UserFragment } } ) } | null }; @@ -566,7 +625,7 @@ export type OAuth2ClientQueryQueryVariables = Exact<{ }>; -export type OAuth2ClientQueryQuery = { __typename?: 'RootQuery', oauth2Client?: { __typename?: 'Oauth2Client', id: string, clientId: string, clientName?: string | null, clientUri?: any | null, tosUri?: any | null, policyUri?: any | null, redirectUris: Array } | null }; +export type OAuth2ClientQueryQuery = { __typename?: 'Query', oauth2Client?: { __typename?: 'Oauth2Client', id: string, clientId: string, clientName?: string | null, clientUri?: any | null, tosUri?: any | null, policyUri?: any | null, redirectUris: Array } | null }; export const BrowserSession_SessionFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BrowserSession_session"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"BrowserSession"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"lastAuthentication"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]}}]} as unknown as DocumentNode; export const BrowserSessionList_UserFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BrowserSessionList_user"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"User"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"browserSessions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"first"},"value":{"kind":"Variable","name":{"kind":"Name","value":"count"}}},{"kind":"Argument","name":{"kind":"Name","value":"after"},"value":{"kind":"Variable","name":{"kind":"Name","value":"cursor"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"edges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"cursor"}},{"kind":"Field","name":{"kind":"Name","value":"node"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"BrowserSession_session"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"BrowserSession_session"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"BrowserSession"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"lastAuthentication"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]}}]} as unknown as DocumentNode; diff --git a/frontend/src/gql/schema.ts b/frontend/src/gql/schema.ts index 6948f7cc..35009770 100644 --- a/frontend/src/gql/schema.ts +++ b/frontend/src/gql/schema.ts @@ -2,13 +2,55 @@ import { IntrospectionQuery } from "graphql"; export default { __schema: { queryType: { - name: "RootQuery", + name: "Query", }, mutationType: { - name: "RootMutations", + name: "Mutation", }, subscriptionType: null, types: [ + { + kind: "OBJECT", + name: "AddEmailPayload", + fields: [ + { + name: "email", + type: { + kind: "NON_NULL", + ofType: { + kind: "OBJECT", + name: "UserEmail", + ofType: null, + }, + }, + args: [], + }, + { + name: "status", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, + }, + args: [], + }, + { + name: "user", + type: { + kind: "NON_NULL", + ofType: { + kind: "OBJECT", + name: "User", + ofType: null, + }, + }, + args: [], + }, + ], + interfaces: [], + }, { kind: "OBJECT", name: "Anonymous", @@ -482,6 +524,82 @@ export default { }, ], }, + { + kind: "OBJECT", + name: "Mutation", + fields: [ + { + name: "addEmail", + type: { + kind: "NON_NULL", + ofType: { + kind: "OBJECT", + name: "AddEmailPayload", + ofType: null, + }, + }, + args: [ + { + name: "input", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, + }, + }, + ], + }, + { + name: "sendVerificationEmail", + type: { + kind: "NON_NULL", + ofType: { + kind: "OBJECT", + name: "SendVerificationEmailPayload", + ofType: null, + }, + }, + args: [ + { + 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: [], + }, { kind: "INTERFACE", name: "Node", @@ -831,83 +949,7 @@ export default { }, { kind: "OBJECT", - name: "RootMutations", - fields: [ - { - name: "addEmail", - type: { - kind: "NON_NULL", - ofType: { - kind: "OBJECT", - name: "UserEmail", - ofType: null, - }, - }, - args: [ - { - name: "input", - type: { - kind: "NON_NULL", - ofType: { - kind: "SCALAR", - name: "Any", - }, - }, - }, - ], - }, - { - name: "sendVerificationEmail", - type: { - kind: "NON_NULL", - ofType: { - kind: "OBJECT", - name: "UserEmail", - ofType: null, - }, - }, - args: [ - { - name: "input", - type: { - kind: "NON_NULL", - ofType: { - kind: "SCALAR", - name: "Any", - }, - }, - }, - ], - }, - { - name: "verifyEmail", - type: { - kind: "NON_NULL", - ofType: { - kind: "OBJECT", - name: "UserEmail", - ofType: null, - }, - }, - args: [ - { - name: "input", - type: { - kind: "NON_NULL", - ofType: { - kind: "SCALAR", - name: "Any", - }, - }, - }, - ], - }, - ], - interfaces: [], - }, - { - kind: "OBJECT", - name: "RootQuery", + name: "Query", fields: [ { name: "browserSession", @@ -1135,6 +1177,48 @@ export default { ], interfaces: [], }, + { + kind: "OBJECT", + name: "SendVerificationEmailPayload", + fields: [ + { + name: "email", + type: { + kind: "NON_NULL", + ofType: { + kind: "OBJECT", + name: "UserEmail", + ofType: null, + }, + }, + args: [], + }, + { + name: "status", + type: { + kind: "NON_NULL", + ofType: { + kind: "SCALAR", + name: "Any", + }, + }, + args: [], + }, + { + name: "user", + type: { + kind: "NON_NULL", + ofType: { + kind: "OBJECT", + name: "User", + ofType: null, + }, + }, + args: [], + }, + ], + interfaces: [], + }, { kind: "OBJECT", name: "UpstreamOAuth2Link", @@ -1836,6 +1920,42 @@ export default { ], 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",