LifeSports Application(ReactNative & Nest.js) - 10. auth-service(4)

16917 ワード

#1 user冗長モジュール


reactネイティブではlocalStorageはサポートされていないため、以下のパッケージをインストールします.
npm install @react-native-async-storage/async-storage
  • ./src/modules/user.js
  • import { createAction, handleActions } from "redux-actions";
    import AsyncStorage from '@react-native-async-storage/async-storage';
    import { takeLatest } from "redux-saga/effects";
    import * as authAPI from '../lib/api/auth';
    import createRequestSaga, {
        createRequestActionTypes,
    } from "../lib/createRequestSaga";
    
    const SAVE_USER = 'user/SAVE_USER';
    const [
        CHECK,
        CHECK_SUCCESS,
        CHECK_FAILURE
    ] = createRequestActionTypes('user/CHECK');
    
    export const saveUser = createAction(SAVE_USER, user => user);
    export const check = createAction(CHECK, userId => userId);
    
    const checkSaga = createRequestSaga(CHECK, authAPI.check);
    
    async function checkFailureSaga() {
        try {
            await AsyncStorage.removeItem('user');
        } catch(e) {
            console.log(e);
        }
    }
    
    export function* userSaga() {
        yield takeLatest(CHECK, checkSaga);
        yield takeLatest(CHECK_FAILURE, checkFailureSaga);
    }
    
    const initialState = {
        user: null,
        checkError: null,
    };
    
    export default handleActions(
        {
            [SAVE_USER]: (state, { payload: user }) => ({
                ...state,
                user,
            }),
            [CHECK_SUCCESS]: (state, { payload: user }) => ({
                ...state,
                user,
                checkError: null
            }),
            [CHECK_FAILURE]: (state, { payload: error }) => ({
                ...state,
                user: null,
                checkError: error,
            }),
        },
        initialState,
    );
    userモジュールの機能を見てみましょう.saveUserの役割は,ログイン時にユーザの状態をreduce stateに保存することである.checkはuserIdを媒介とする方法であり,検査するユーザが適切なユーザであるか否かを確認するために用いられる.checkFailureSagaでは、ユーザーのステータスを削除する役割を果たします.
    次にauthモジュールで次のアクションを作成します.
  • ./src/modules/auth.js
  • ...
    const [
        INFO,
        INFO_SUCCESS,
        INFO_FAILURE,
    ] = createRequestActionTypes('auth/INFO');
    
    ...
    export const info = createAction(INFO, userId => userId);
    
    ...
    const infoSaga = createRequestSaga(INFO, authAPI.getUser);
    
    export function* authSaga() {
        yield takeLatest(REGISTER, registerSaga);
        yield takeLatest(LOGIN, loginSaga);
        yield takeLatest(INFO, infoSaga);
    }
    
    ...
    
    const auth = handleActions(
        {
            ...
            [INFO_SUCCESS]: (state, { payload: auth }) => ({
                ...state,
                authError: null,
                auth,
            }),
            [INFO_FAILURE]: (state, { payload: error }) => ({
                ...state,
                authError: error,
            }),
        },
        initialState,
    );
    
    export default auth;
  • ./src/modules/index.js
  • import { combineReducers } from "redux";
    import { all } from "redux-saga/effects";
    import auth, { authSaga } from './auth';
    import loading from "./loading";
    import user, { userSaga } from "./user";
    
    const rootReducer = combineReducers({
        auth,
        loading,
        user,
    });
    
    export function* rootSaga() {
        yield all([
            authSaga(),
            userSaga()
        ]);
    };
    
    export default rootReducer;
  • ./App.js
  • import React from 'react';
    import AsyncStorage from '@react-native-async-storage/async-storage';
    import { Provider } from 'react-redux';
    import { createStore, applyMiddleware } from 'redux';
    import { composeWithDevTools } from 'redux-devtools-extension';
    import createSagaMiddleware from 'redux-saga';
    import rootReducer, { rootSaga } from './src/modules';
    import { saveUser } from './src/modules/user';
    import StackNavigatior from './src/navigator/MainNavigation';
    
    const sagaMiddleware = createSagaMiddleware();
    const store = createStore(
      rootReducer, 
      composeWithDevTools(applyMiddleware(sagaMiddleware)),
    );
    
    async function loadUser() {
      try {
        const user = JSON.parse(await AsyncStorage.getItem('user'));
        
        if(!user) {
          return;
        }
    
        store.dispatch(saveUser(user));
    
        const { userId } = user;
        
        store.dispatch(check(userId));
      } catch(e) {
        console.log(e);
      }
    }
    
    sagaMiddleware.run(rootSaga);
    loadUser();
    
    const App = () => {
      return(
        <Provider store={ store }>
          <StackNavigatior />
        </Provider>
      );
    };
    
    export default App;
    loadUserメソッドを作成して、アプリケーションの起動時にすぐにユーザーステータスをロードします.
  • ./src/lib/api/auth.js
  • ...
    export const getUser = async userId => client.get(`http://10.0.2.2:7000/auth-service/${userId}`, {
        headers: {
            'Authorization': 'Bearer ' + JSON.parse(await AsyncStorage.getItem('token'))       
        }
    });
    
    export const check = async userId => client.get(`http://10.0.2.2:7000/auth-service/${userId}/check`, {
        headers: {
            'Authorization': 'Bearer ' + JSON.parse(await AsyncStorage.getItem('token'))       
        }
    });
    checkメソッドはjwtタグをヘッダーに配置し、適切なユーザーであることを確認します.
    auth-serviceのコントローラにcheckに関連する方法を追加します.
  • ./src/app.controller.ts
  • ...
    
    @Controller('auth-service')
    export class AppController {
        ...
    
        @Get(':userId/check')
        public async check(@Param('userId') userId: string): Promise<any> {
            if(userId === null) {
                return await Object.assign({
                    status: HttpStatus.UNAUTHORIZED,
                    payload: null,
                    message: "Not valid user"
                });
            }
    
            const result: any = await this.userService.getUser(userId);
    
            return await Object.assign({
                status: HttpStatus.OK,
                payload: Builder(ResponseUser).email(result.payload.email)
                                              .nickname(result.payload.nickname)
                                              .phoneNumber(result.payload.phoneNumber)
                                              .userId(result.payload.userId)
                                              .build(),
                message: "Check user!"
            });
        }
    }
    ユーザーモジュールが完了しました.次に、ユーザーモジュールをログインフォームにロードします.
  • ./src/pages/auth/components/
  • import React, { useEffect, useState } from 'react';
    import { useDispatch, useSelector } from 'react-redux';
    import { View, StyleSheet } from 'react-native';
    import AsyncStorage from '@react-native-async-storage/async-storage';
    import StyledBorderButton from '../../../styles/common/StyledBorderButton';
    import StyledFullButton from '../../../styles/common/StyledFullButton';
    import StyledTextInput from '../../../styles/common/StyledTextInput';
    import palette from '../../../styles/palette';
    import { changeField, info, initializeForm, login } from '../../../modules/auth';
    import ErrorMessage from '../../../styles/common/ErrorMessage';
    import { check } from '../../../modules/user';
    
    const LoginForm = ({ navigation }) => {
        ...
    
        useEffect(() => {
            if(auth) {
                const { userId } = auth;
    
                dispatch(check(userId));
            }
    
            if(authError) {
                setError("Not valid user, check again email and password");
    
                return;
            }
        }, [auth, authError, dispatch]);
    
        useEffect(async () => {
            if(user) {
                try {
                    setError(null);
            
                    dispatch(initializeForm('login'));
                
                    await AsyncStorage.setItem('user', JSON.stringify(user))
                
                    dispatch(initializeForm('auth'));
    
                    navigation.navigate('Tab');
                } catch(e) {
                    console.log(e);
                }
            }
        }, [user]);
    
        useEffect(async () => {
            if(auth) {
                if(auth.token) {
                    const { token } = auth;
                    const { userId } = auth;
    
                    await AsyncStorage.setItem('token', JSON.stringify(token));
                    
                    dispatch(info(userId));
                }
            }
        }, [dispatch, auth]);
        
        ...
    };
    
    ...
    
    export default LoginForm;
    では、これらのコードをもとに、ユーザーの状態がよく保存されているかどうかを確認します.


    ログインすると、ユーザーのステータスが表示されます.その後、私のページにユーザーのステータス値を表示し、ログアウトを実施しようとします.
  • ./src/pages/user/components/MyPageHeader.js
  • import React from 'react';
    import {
        StyleSheet,
        View,
        Text
    } from 'react-native';
    import { useSelector } from 'react-redux';
    import palette from '../../../styles/palette';
    
    const MyPageHeader = () => {
        const { user } = useSelector(({ user }) => ({ user: user.user }));
    
        return(
            <View style={ styles.container }>
                <Text style={ styles.text }>
                    { user ? user.nickname : null }
                </Text>
                <Text>
                    { user ? user.email : null }
                </Text>
            </View>
        );
    };
    
    const styles = StyleSheet.create({
        container: {
            width: 420,
            height: 100,
            alignItems: 'center',
            justifyContent: 'center',
            backgroundColor: palette.gray[3],
        },
        text: {
            fontWeight: 'bold',
            fontSize: 17,
        },
    });
    
    export default MyPageHeader;
    MyPageHeaderに表示される簡単なユーザ情報を示し,ログアウトに関連するモジュールと方法を作成した.
    まず、authサービスコントローラで次の方法を定義します.
  • ./src/app.controller.ts
  • ...
    
    @Controller('auth-service')
    export class AppController {
        ...
    
        @Post('logout')
        public async logout(): Promise<any> {
            try {
                return await Object.assign({
                    status: HttpStatus.NO_CONTENT,
                    payload: null,
                    message: "Success logout!"
                });
            } catch(err) {
                return await Object.assign({
                    status: statusConstants.ERROR,
                    payload: null,
                    message: "Error message: " + err
                });
            }
        }
    }
    次に、react nativeで関連付けられたAPIエンドポイントメソッドを定義します.
  • ./src/lib/api/auth.js
  • ...
    export const logout = () => client.post('http://10.0.2.2:7000/auth-service/logout');
    モジュールをログアウトします.
  • ./src/modules/user.js
  • import { createAction, handleActions } from "redux-actions";
    import AsyncStorage from '@react-native-async-storage/async-storage';
    import { takeLatest, call } from "redux-saga/effects";
    import * as authAPI from '../lib/api/auth';
    import createRequestSaga, {
        createRequestActionTypes,
    } from "../lib/createRequestSaga";
    
    ...
    const LOGOUT = 'user/LOGOUT';
    
    ...
    export const logout = createAction(LOGOUT);
    
    ...
    
    async function logoutSaga() {
        try {
            call(authAPI.logout);
    
            await AsyncStorage.removeItem('user');
        } catch(err) {
            console.log(err);
        }
    }
    
    export function* userSaga() {
        yield takeLatest(CHECK, checkSaga);
        yield takeLatest(CHECK_FAILURE, checkFailureSaga);
        yield takeLatest(LOGOUT, logoutSaga);
    }
    
    ...
    
    export default handleActions(
        {
            ...
            [LOGOUT]: state => ({
                ...state,
                user: null
            }),
        },
        initialState,
    );
    このモジュールをLogoutButton構成部品にロードしてテストします.
  • ./src/pages/user/components/LogoutButton.js
  • import React from 'react';
    import { 
        StyleSheet,
        Text, 
        TouchableOpacity 
    } from 'react-native';
    import { useDispatch } from 'react-redux';
    import { logout } from '../../../modules/user';
    import palette from '../../../styles/palette';
    
    const LogoutButton = () => {
        const navigation = useNavigation();
        const dispatch = useDispatch();
        const onLogout = e => {
            e.preventDefault();
    
            dispatch(logout());
    
            navigation.navigate('SignIn');
        };
    
        return(
            <TouchableOpacity style={ styles.container }
                              onPress={ onLogout }
            >
                <Text style={ styles.text }>
                    Logout
                </Text>
            </TouchableOpacity>
        );
    };
    
    const styles = StyleSheet.create({
        container: {
            flexDirection: 'row',
            alignItems: 'center',
            justifyContent: 'center',
            width: 350,
            height: 50,
            borderRadius: 30,
            borderColor: palette.blue[4],
            borderWidth: 2,
            backgroundColor: palette.white[0],
            marginTop: 10,
        },
        text: {
            fontWeight: 'bold',
            fontSize: 15,
            color: palette.blue[4],
        },
    });
    
    export default LogoutButton;



    テスト結果は,ユーザの状態が良好に削除されていることを示した.次の記事では、管理データに関連するローカルサーバを表示し、mapサービスからこのデータを取得しようとします.