Jblew/firestore-roles-redux-module

View on GitHub
src/module/actions/EpicActionsImpl.ts

Summary

Maintainability
A
0 mins
Test Coverage
// tslint:disable no-console
import * as _ from "lodash";
import ow from "ow";
import { Dispatch } from "redux";

import { Account } from "../../Account";
import { AuthAdapter } from "../../adapter/AuthAdapter";
import { RolesAdapter } from "../../adapter/RolesAdapter";
import { Configuration } from "../../Configuration";
import { ThunkDispatch } from "../../thunk";
import { ContainingStoreState } from "../ContainingStoreState";

import { EpicActions } from "./EpicActions";
import { PlainActions } from "./PlainActions";
import { PrivateEpicActions } from "./PrivateEpicActions";

export class EpicActionsImpl implements EpicActions, PrivateEpicActions {
    private callbacks: Configuration.AuthCallbacks;
    private rolesAdapter: RolesAdapter;
    private authAdapter: AuthAdapter;

    public constructor(callbacks: Configuration.AuthCallbacks, rolesAdapter: RolesAdapter, authAdapter: AuthAdapter) {
        this.callbacks = callbacks;
        this.rolesAdapter = rolesAdapter;
        this.authAdapter = authAdapter;
    }

    public initialize() {
        const onAuthenticated = (dispatch: ThunkDispatch, user: firebase.UserInfo) => {
            const account = Account.fromFirebaseUserInfo(user);

            dispatch(this.ensureAccountRegistered(account));
            dispatch(PlainActions.Actions.authSuccess(account));
            this.callbacks.onAuthenticated(account);
        };

        const onNotAuthenticated = (dispatch: ThunkDispatch) => {
            dispatch(PlainActions.Actions.authNotAuthenticated());
            this.callbacks.onNotAuthenticated();
        };

        const onError = (dispatch: ThunkDispatch, msg: string) => {
            dispatch(PlainActions.Actions.authFailure(msg));
            this.callbacks.onError(msg);
        };

        return async (dispatch: Dispatch, getState: () => ContainingStoreState) => {
            dispatch(PlainActions.Actions.authLoading());

            await this.authAdapter.initialize({
                onAuthenticated: user => onAuthenticated(dispatch, user),
                onNotAuthenticated: () => onNotAuthenticated(dispatch),
                onError: msg => onError(dispatch, msg),
            });

            return this.initializeActionIntent();
        };
    }

    public logout() {
        return async (dispatch: Dispatch) => {
            dispatch(PlainActions.Actions.authLoading());
            try {
                await this.authAdapter.signOut();
                dispatch(PlainActions.Actions.authNotAuthenticated());
            } catch (error) {
                const errMsg = `Could not log out: ${error.message}`;
                dispatch(PlainActions.Actions.authFailure(errMsg));
                this.callbacks.onError(errMsg);
            }
            return this.logoutActionIntent();
        };
    }

    public checkRole(role: string) {
        const validateInput = (inputRole: string, account: object | null) => {
            ow(inputRole, "role", ow.string.nonEmpty);
            if (!account) throw new Error("Cannot get role before authentication");
        };

        const doCheckRole = async (uid: string) => {
            return await this.rolesAdapter.hasRole(uid, role);
        };

        const doCheckRoleRequest = async (uid: string) => {
            return await this.rolesAdapter.isRoleRequestedByUser(uid, role);
        };

        return async (dispatch: Dispatch, getState: () => ContainingStoreState) => {
            const state = getState();
            try {
                validateInput(role, state.rolesAuth.account);

                const hasRole = await doCheckRole(state.rolesAuth.account!.uid);
                dispatch(PlainActions.Actions.setRoleStatus(role, hasRole));

                if (!hasRole) {
                    const isRequestingRole = await doCheckRoleRequest(state.rolesAuth.account!.uid);
                    dispatch(PlainActions.Actions.setRoleRequestStatus(role, isRequestingRole));
                }
            } catch (error) {
                this.callbacks.onError(`Could not ensure user is registered: ${error.message}`);
            }
            return this.checkRoleActionIntent(role);
        };
    }

    public ensureAccountRegistered(account: Account) {
        const doEnsureRegistered = async (user: Account) => {
            const userExists = await this.rolesAdapter.userExists(user.uid);
            if (!userExists) {
                await this.rolesAdapter.registerUser(user);
            }
        };

        return async (dispatch: Dispatch) => {
            try {
                await doEnsureRegistered(account);
            } catch (error) {
                this.callbacks.onError(`Could not ensure user is registered: ${error.message}`);
            }
            return this.ensureAccountRegisteredActionIntent(account);
        };
    }

    /**
     * actions
     */
    private initializeActionIntent(): EpicActions.InitializeAction {
        return { type: EpicActions.INITIALIZE };
    }

    private logoutActionIntent(): EpicActions.LogoutAction {
        return { type: EpicActions.LOGOUT };
    }

    private checkRoleActionIntent(role: string): EpicActions.CheckRoleAction {
        return { type: EpicActions.CHECK_ROLE, role };
    }

    private ensureAccountRegisteredActionIntent(account: Account): PrivateEpicActions.EnsureAccountRegisteredAction {
        return { type: PrivateEpicActions.ENSURE_ACCOUNT_REGISTERED, account };
    }
}