Просмотр исходного кода

Feature: Mobile - Added basic GraphQL mutation error handling.

Martin Gruner 3 лет назад
Родитель
Сommit
2c2383cfab

+ 7 - 0
app/frontend/apps/mobile/views/Login.vue

@@ -116,6 +116,13 @@ const login = (): void => {
     .then(() => {
       router.replace('/')
     })
+    .catch((errors) => {
+      const { notify } = useNotifications()
+      notify({
+        message: errors[0],
+        type: NotificationTypes.WARN,
+      })
+    })
 }
 </script>
 

+ 1 - 0
app/frontend/common/graphql/api.ts

@@ -22,6 +22,7 @@ export const LoginDocument = gql`
     mutation login($login: String!, $password: String!, $fingerprint: String!) {
   login(login: $login, password: $password, fingerprint: $fingerprint) {
     sessionId
+    errors
   }
 }
     `;

+ 1 - 0
app/frontend/common/graphql/mutations/login.graphql

@@ -1,5 +1,6 @@
 mutation login($login: String!, $password: String!, $fingerprint: String!) {
   login(login: $login, password: $password, fingerprint: $fingerprint) {
     sessionId
+    errors
   }
 }

+ 6 - 2
app/frontend/common/graphql/types.ts

@@ -74,13 +74,17 @@ export type Locale = Node & {
 /** Autogenerated return type of Login */
 export type LoginPayload = {
   __typename?: 'LoginPayload';
+  /** Errors encountered during execution of the mutation. */
+  errors?: Maybe<Array<Scalars['String']>>;
   /** The current session */
-  sessionId: Scalars['String'];
+  sessionId?: Maybe<Scalars['String']>;
 };
 
 /** Autogenerated return type of Logout */
 export type LogoutPayload = {
   __typename?: 'LogoutPayload';
+  /** Errors encountered during execution of the mutation. */
+  errors?: Maybe<Array<Scalars['String']>>;
   /** Was the logout successful? */
   success: Scalars['Boolean'];
 };
@@ -530,7 +534,7 @@ export type LoginMutationVariables = Exact<{
 }>;
 
 
-export type LoginMutation = { __typename?: 'Mutations', login?: { __typename?: 'LoginPayload', sessionId: string } | null | undefined };
+export type LoginMutation = { __typename?: 'Mutations', login?: { __typename?: 'LoginPayload', sessionId?: string | null | undefined, errors?: Array<string> | null | undefined } | null | undefined };
 
 export type LogoutMutationVariables = Exact<{ [key: string]: never; }>;
 

+ 6 - 0
app/frontend/common/stores/authenticated.ts

@@ -52,6 +52,10 @@ const useAuthenticatedStore = defineStore('authenticated', {
 
       const result = await loginMutation.send()
 
+      if (result?.login?.errors) {
+        return Promise.reject(result?.login?.errors)
+      }
+
       const newSessionId = result?.login?.sessionId || null
 
       if (newSessionId) {
@@ -64,6 +68,8 @@ const useAuthenticatedStore = defineStore('authenticated', {
           useSessionUserStore().getCurrentUser(),
         ])
       }
+
+      return Promise.resolve()
     },
   },
   shareState: {

+ 7 - 3
app/frontend/common/stores/translations.ts

@@ -79,12 +79,16 @@ const useTranslationsStore = defineStore('translations', {
       const query = getTranslationsQuery()
 
       const result = await query.loadedResult()
-      if (result?.translations?.isCacheStillValid) {
+      if (!result?.translations) {
+        return
+      }
+
+      if (result.translations.isCacheStillValid) {
         this.value = cachedData
       } else {
         this.value = {
-          cacheKey: result?.translations?.cacheKey || 'CACHE_EMPTY',
-          translations: result?.translations?.translations,
+          cacheKey: result.translations.cacheKey || 'CACHE_EMPTY',
+          translations: result.translations.translations,
         }
         setCache(locale, this.value)
       }

+ 6 - 0
app/graphql/gql/mutations/base_mutation.rb

@@ -10,6 +10,8 @@ module Gql::Mutations
     object_class   Gql::Types::BaseObject
     # input_object_class Gql::Types::BaseInputObject
 
+    field :errors, [String], description: 'Errors encountered during execution of the mutation.'
+
     # Override this for mutations that don't need CSRF verification.
     def self.requires_csrf_verification?
       true
@@ -33,5 +35,9 @@ module Gql::Mutations
       schema.field field_name, mutation: self
     end
 
+    def error_response(*errors)
+      { errors: errors }
+    end
+
   end
 end

+ 10 - 10
app/graphql/gql/mutations/login.rb

@@ -4,7 +4,7 @@ module Gql::Mutations
   class Login < BaseMutation
     description 'Performs a user login to create a session'
 
-    field :session_id, String, null: false, description: 'The current session'
+    field :session_id, String, description: 'The current session'
 
     argument :login, String, required: true, description: 'User name'
     argument :password, String, required: true, description: 'Password'
@@ -14,31 +14,31 @@ module Gql::Mutations
     def resolve(...)
 
       # Register user for subsequent auth checks.
-      context[:current_user] = authenticate(...)
+      authenticate(...)
 
-      {
-        session_id: context[:controller].session.id
-      }
+      if !context[:current_user]
+        return error_response(__('Wrong login or password combination.'))
+      end
+
+      { session_id: context[:controller].session.id }
     end
 
     private
 
     def authenticate(login:, password:, fingerprint:) # rubocop:disable Metrics/AbcSize
       auth = Auth.new(login, password)
-      user = auth&.user
-
       if !auth.valid?
-        raise __('Wrong login or password combination.')
+        return
       end
 
+      user = auth&.user
       context[:controller].session.delete(:switched_from_user_id)
 
       # Fingerprint param is expected for session logins.
       context[:controller].params[:fingerprint] = fingerprint
       # authentication_check_prerequesits is private
       context[:controller].send(:authentication_check_prerequesits, user, 'session', {})
-
-      user
+      context[:current_user] = user
     end
   end
 end

+ 1 - 5
spec/graphql/gql/mutations/login_spec.rb

@@ -45,11 +45,7 @@ RSpec.describe Gql::Mutations::Login, type: :request do
       let(:password) { 'wrong' }
 
       it 'fails with error message' do
-        expect(graphql_response['errors'][0]).to include('message' => 'Wrong login or password combination.')
-      end
-
-      it 'fails with error type' do
-        expect(graphql_response['errors'][0]['extensions']).to include({ 'type' => 'RuntimeError' })
+        expect(graphql_response['data']['login']['errors']).to eq(['Wrong login or password combination.'])
       end
     end