ahbeng/NUSMods

View on GitHub
website/src/middlewares/requests-middleware.ts

Summary

Maintainability
B
4 hrs
Test Coverage
import type { Middleware } from 'redux';
import type { AxiosRequestConfig, AxiosError } from 'axios';
import axios from 'axios';

import type { State } from 'types/state';
import type { Dispatch } from 'types/redux';
import { FAILURE, REQUEST, SUCCESS } from 'types/reducers';
import { API_REQUEST, RequestsDispatchExt } from 'actions/requests';

export type ActionType<Action extends string, Type extends string> = Action & { __type: Type };

// eslint-disable-next-line @typescript-eslint/ban-types
type DefaultMeta = {};

export type RequestAction<Type extends string, Meta = DefaultMeta> = {
  type: ActionType<Type, typeof REQUEST>;
  payload: AxiosRequestConfig;
  meta?: Meta;
};

export type SuccessAction<Type extends string, Response, Meta = DefaultMeta> = {
  type: ActionType<Type, typeof SUCCESS>;
  payload: Response;
  meta?: Meta & {
    requestStatus: typeof SUCCESS;
    responseHeaders: { [header: string]: string };
  };
};

export type FailureAction<Type extends string, Meta = DefaultMeta> = {
  type: ActionType<Type, typeof FAILURE>;
  payload: Error;
  meta?: Meta & {
    requestStatus: typeof FAILURE;
  };
};

export type RequestActions<Type extends string, Response, Meta = DefaultMeta> =
  | RequestAction<Type, Meta>
  | SuccessAction<Type, Response, Meta>
  | FailureAction<Type, Meta>;

function makeRequest(request: AxiosRequestConfig) {
  return axios.request({
    ...request,
    headers: {
      'Content-Type': 'application/json',
    },
  });
}

export function SUCCESS_KEY<Type extends string>(key: Type): ActionType<Type, typeof SUCCESS> {
  return (key + SUCCESS) as ActionType<Type, typeof SUCCESS>;
}

export function FAILURE_KEY<Type extends string>(key: Type): ActionType<Type, typeof FAILURE> {
  return (key + FAILURE) as ActionType<Type, typeof FAILURE>;
}

const requestMiddleware: Middleware<RequestsDispatchExt, State, Dispatch> =
  () => (next) => (action) => {
    if (!action.meta || !action.meta[API_REQUEST]) {
      // Non-api request action
      return next(action);
    }

    // type     is the base action type that will trigger
    // payload  is the request body to be processed
    const { type, payload, meta } = action;

    // Propagate the start of the request
    next({
      type: type + REQUEST,
      payload,
      meta: {
        ...meta,
        requestStatus: REQUEST,
      },
    });

    // Propagate the response of the request.
    return makeRequest(payload).then(
      (response) => {
        next({
          type: SUCCESS_KEY(type),
          payload: response.data,
          meta: {
            ...meta,
            requestStatus: SUCCESS,
            request: payload,
            responseHeaders: response.headers,
          },
        });

        return response.data;
      },
      (error: AxiosError) => {
        next({
          type: FAILURE_KEY(type),
          payload: error,
          meta: {
            ...meta,
            requestStatus: FAILURE,
            request: payload,
          },
        });

        throw error;
      },
    );
  };

export default requestMiddleware;