From e0bba90d2e363052f1603e832a7937cfd268d641 Mon Sep 17 00:00:00 2001
From: muerwre <gotham48@gmail.com>
Date: Tue, 6 Aug 2019 18:21:19 +0700
Subject: [PATCH] auth

---
 .eslintrc                    |  34 -----------
 .eslintrc.js                 | 107 +++++++++++++++++++++++++++++++++++
 package-lock.json            | 101 ++++++++++++++++++++++++++++++---
 package.json                 |   2 +
 src/redux/auth/api.ts        |  10 +---
 src/redux/auth/reducer.ts    |   8 +--
 src/redux/auth/sagas.ts      |  43 +++++++++-----
 src/redux/auth/selectors.ts  |   3 +-
 src/redux/auth/transforms.ts |  24 ++------
 src/redux/auth/types.ts      |   2 +-
 src/redux/node/reducer.ts    |  15 +++++
 src/utils/api/index.ts       |   4 +-
 12 files changed, 264 insertions(+), 89 deletions(-)
 delete mode 100644 .eslintrc
 create mode 100644 .eslintrc.js
 create mode 100644 src/redux/node/reducer.ts

diff --git a/.eslintrc b/.eslintrc
deleted file mode 100644
index 5a6e3033..00000000
--- a/.eslintrc
+++ /dev/null
@@ -1,34 +0,0 @@
-{
-  "extends": "airbnb",
-  "parser": "babel-eslint",
-  "plugins": [
-    "react",
-    "jsx-a11y",
-    "import"
-  ],
-  "rules": {
-    "comma-dangle": 0,
-    "no-restricted-syntax": 1,
-    "new-cap": 1,
-    "no-continue": 1,
-    "no-underscore-dangle": 1,
-    "global-require": 1,
-    "react/no-multi-comp": 1,
-    "react/jsx-filename-extension": 0,
-    "camelcase": 1,
-    "import/no-unresolved": 1,
-    "import/prefer-default-export": 1,
-    "import/extensions": 1,
-    "no-return-assign": 1,
-    "max-len": 1,
-    "jsx-a11y/no-static-element-interactions": 0,
-    "jsx-a11y/click-events-have-key-events": 0,
-    "jsx-a11y/interactive-supports-focus": 0
-  },
-  "globals": {
-    "document": false,
-    "window": false,
-    "HTMLInputElement": false,
-    "HTMLDivElement": false
-  }
-}
diff --git a/.eslintrc.js b/.eslintrc.js
new file mode 100644
index 00000000..4b23f3e5
--- /dev/null
+++ b/.eslintrc.js
@@ -0,0 +1,107 @@
+// module.exports = {
+//   extends: ['airbnb', 'airbnb-base', 'plugin:@typescript-eslint/recommended'],
+//   // "parser": "babel-eslint",
+//   parser: '@typescript-eslint/parser',
+//   parserOptions: {
+//     ecmaFeatures: {
+//       jsx: true
+//     },
+//     project: './tsconfig.json'
+//   },
+//   plugins: ['@typescript-eslint', 'react', 'jsx-a11y', 'import', 'react-hooks'],
+//   rules: {
+//     indent: ['error', 2],
+//     '@typescript-eslint/indent': ['error', 2],
+//     'comma-dangle': 0,
+//     'no-restricted-syntax': 1,
+//     'new-cap': 1,
+//     'no-continue': 1,
+//     'no-underscore-dangle': 1,
+//     'global-require': 1,
+//     'react/no-multi-comp': 1,
+//     'react/jsx-filename-extension': 0,
+//     camelcase: 1,
+//     'import/no-unresolved': 1,
+//     'import/prefer-default-export': 1,
+//     'import/extensions': 1,
+//     'no-return-assign': 1,
+//     'max-len': 1,
+//     'jsx-a11y/no-static-element-interactions': 0,
+//     'jsx-a11y/click-events-have-key-events': 0,
+//     'jsx-a11y/interactive-supports-focus': 0,
+//     'react-hooks/rules-of-hooks': 'error',
+//     'react-hooks/exhaustive-deps': 'warn',
+//     'no-nested-ternary': 1
+//   },
+//   globals: {
+//     document: false,
+//     window: false,
+//     HTMLInputElement: false,
+//     HTMLDivElement: false
+//   }
+// };
+module.exports = {
+  extends: ['plugin:@typescript-eslint/recommended'],
+  plugins: ['import', '@typescript-eslint'],
+  parser: '@typescript-eslint/parser',
+  parserOptions: {
+    ecmaFeatures: {
+      jsx: true,
+    },
+    project: './tsconfig.json',
+  },
+  globals: {
+    Reactotron: true,
+  },
+  rules: {
+    curly: ['error', 'all'],
+    'valid-jsdoc': 'error',
+    'linebreak-style': 'off',
+    'no-console': 'off',
+    'object-curly-newline': 'off',
+    'no-unused-expressions': 'off',
+    'no-unused-vars': 'off',
+    'prefer-destructuring': [
+      'error',
+      {
+        VariableDeclarator: {
+          object: true,
+        },
+      },
+    ],
+    'function-paren-newline': ['error', 'consistent'],
+    'lines-between-class-members': ['error', 'always', { exceptAfterSingleLine: true }],
+    'no-multiple-empty-lines': ['error', { max: 1, maxBOF: 0, maxEOF: 0 }],
+
+    'eslint-comments/no-unlimited-disable': 'off',
+
+    'import/no-unresolved': 'off',
+    'import/extensions': 'off',
+    'import/prefer-default-export': 'off',
+
+    'prettier/prettier': [
+      'error',
+      {
+        singleQuote: true,
+        parser: 'flow',
+        trailingComma: 'all',
+        printWidth: 100,
+      },
+      '@format',
+    ],
+
+    '@typescript-eslint/indent': ['error', 2],
+    '@typescript-eslint/explicit-function-return-type': [
+      'off',
+      {
+        allowExpressions: true,
+        allowTypedFunctionExpressions: true,
+        allowHigherOrderFunctions: true,
+      },
+    ],
+    '@typescript-eslint/no-explicit-any': 'off',
+    '@typescript-eslint/no-var-requires': 'off',
+    '@typescript-eslint/explicit-member-accessibility': 'off',
+    '@typescript-eslint/no-empty-interface': 'off',
+  },
+};
diff --git a/package-lock.json b/package-lock.json
index 224de817..3f0203a3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2599,6 +2599,16 @@
       "resolved": "https://registry.npmjs.org/@types/classnames/-/classnames-2.2.7.tgz",
       "integrity": "sha512-rzOhiQ55WzAiFgXRtitP/ZUT8iVNyllEpylJ5zHzR4vArUvMB39GTk+Zon/uAM0JxEFAWnwsxC2gH8s+tZ3Myg=="
     },
+    "@types/eslint-visitor-keys": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz",
+      "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag=="
+    },
+    "@types/json-schema": {
+      "version": "7.0.3",
+      "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.3.tgz",
+      "integrity": "sha512-Il2DtDVRGDcqjDtE+rF8iqg1CArehSK84HZJCT7AMITlyXRBpuPhqGLDQMowraqqu1coEaimg4ZOqggt6L6L+A=="
+    },
     "@types/node": {
       "version": "11.11.1",
       "resolved": "https://registry.npmjs.org/@types/node/-/node-11.11.1.tgz",
@@ -2624,6 +2634,74 @@
         "csstype": "^2.2.0"
       }
     },
+    "@typescript-eslint/eslint-plugin": {
+      "version": "1.13.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-1.13.0.tgz",
+      "integrity": "sha512-WQHCozMnuNADiqMtsNzp96FNox5sOVpU8Xt4meaT4em8lOG1SrOv92/mUbEHQVh90sldKSfcOc/I0FOb/14G1g==",
+      "requires": {
+        "@typescript-eslint/experimental-utils": "1.13.0",
+        "eslint-utils": "^1.3.1",
+        "functional-red-black-tree": "^1.0.1",
+        "regexpp": "^2.0.1",
+        "tsutils": "^3.7.0"
+      },
+      "dependencies": {
+        "regexpp": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz",
+          "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw=="
+        },
+        "tsutils": {
+          "version": "3.17.0",
+          "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.0.tgz",
+          "integrity": "sha512-fyveWOtAXfumAxIqkcMHuPaaVyLBKjB8Y00ANZkqh+HITBAQscCbQIHwwBTJdvQq7RykLEbOPcUUnJ16X4NA0g==",
+          "requires": {
+            "tslib": "^1.8.1"
+          }
+        }
+      }
+    },
+    "@typescript-eslint/experimental-utils": {
+      "version": "1.13.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-1.13.0.tgz",
+      "integrity": "sha512-zmpS6SyqG4ZF64ffaJ6uah6tWWWgZ8m+c54XXgwFtUv0jNz8aJAVx8chMCvnk7yl6xwn8d+d96+tWp7fXzTuDg==",
+      "requires": {
+        "@types/json-schema": "^7.0.3",
+        "@typescript-eslint/typescript-estree": "1.13.0",
+        "eslint-scope": "^4.0.0"
+      },
+      "dependencies": {
+        "eslint-scope": {
+          "version": "4.0.3",
+          "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz",
+          "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==",
+          "requires": {
+            "esrecurse": "^4.1.0",
+            "estraverse": "^4.1.1"
+          }
+        }
+      }
+    },
+    "@typescript-eslint/parser": {
+      "version": "1.13.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-1.13.0.tgz",
+      "integrity": "sha512-ITMBs52PCPgLb2nGPoeT4iU3HdQZHcPaZVw+7CsFagRJHUhyeTgorEwHXhFf3e7Evzi8oujKNpHc8TONth8AdQ==",
+      "requires": {
+        "@types/eslint-visitor-keys": "^1.0.0",
+        "@typescript-eslint/experimental-utils": "1.13.0",
+        "@typescript-eslint/typescript-estree": "1.13.0",
+        "eslint-visitor-keys": "^1.0.0"
+      }
+    },
+    "@typescript-eslint/typescript-estree": {
+      "version": "1.13.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-1.13.0.tgz",
+      "integrity": "sha512-b5rCmd2e6DCC6tCTN9GSUAuxdYwCM/k/2wdjHGrIRGPSJotWMCe/dGpi66u42bhuh8q3QBzqM4TMA1GUUCJvdw==",
+      "requires": {
+        "lodash.unescape": "4.0.1",
+        "semver": "5.5.0"
+      }
+    },
     "@webassemblyjs/ast": {
       "version": "1.8.5",
       "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz",
@@ -7286,11 +7364,18 @@
         "estraverse": "^4.1.1"
       }
     },
+    "eslint-utils": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.0.tgz",
+      "integrity": "sha512-7ehnzPaP5IIEh1r1tkjuIrxqhNkzUJa9z3R92tLJdZIVdWaczEhr3EbhGtsMrVxi1KeR8qA7Off6SWc5WNQqyQ==",
+      "requires": {
+        "eslint-visitor-keys": "^1.0.0"
+      }
+    },
     "eslint-visitor-keys": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz",
-      "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==",
-      "dev": true
+      "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ=="
     },
     "espree": {
       "version": "3.5.4",
@@ -7321,7 +7406,6 @@
       "version": "4.2.1",
       "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz",
       "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==",
-      "dev": true,
       "requires": {
         "estraverse": "^4.1.0"
       }
@@ -7329,8 +7413,7 @@
     "estraverse": {
       "version": "4.2.0",
       "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz",
-      "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=",
-      "dev": true
+      "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM="
     },
     "esutils": {
       "version": "2.0.2",
@@ -8507,8 +8590,7 @@
     "functional-red-black-tree": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
-      "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
-      "dev": true
+      "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc="
     },
     "gauge": {
       "version": "2.7.4",
@@ -10379,6 +10461,11 @@
       "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
       "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ="
     },
+    "lodash.unescape": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/lodash.unescape/-/lodash.unescape-4.0.1.tgz",
+      "integrity": "sha1-vyJJiGzlFM2hEvrpIYzcBlIR/Jw="
+    },
     "lodash.uniq": {
       "version": "4.5.0",
       "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
diff --git a/package.json b/package.json
index a252d778..614618ad 100644
--- a/package.json
+++ b/package.json
@@ -58,6 +58,8 @@
     "@types/classnames": "^2.2.7",
     "@types/node": "^11.9.0",
     "@types/react": "16.8.23",
+    "@typescript-eslint/eslint-plugin": "^1.13.0",
+    "@typescript-eslint/parser": "^1.13.0",
     "axios": "^0.18.0",
     "babel-runtime": "^6.26.0",
     "classnames": "^2.2.6",
diff --git a/src/redux/auth/api.ts b/src/redux/auth/api.ts
index 6253169f..7b55fbbd 100644
--- a/src/redux/auth/api.ts
+++ b/src/redux/auth/api.ts
@@ -7,8 +7,7 @@ import {
 } from '~/utils/api';
 import { API } from '~/constants/api';
 import { IResultWithStatus } from '~/redux/types';
-import { authMeTransform, userLoginTransform } from '~/redux/auth/transforms';
-import { IUser } from '~/redux/auth/types';
+import { userLoginTransform } from '~/redux/auth/transforms';
 
 export const apiUserLogin = ({
   username,
@@ -22,10 +21,3 @@ export const apiUserLogin = ({
     .then(resultMiddleware)
     .catch(errorMiddleware)
     .then(userLoginTransform);
-
-export const getAuthSelf = ({ access }): Promise<IResultWithStatus<{ user: IUser }>> =>
-  api
-    .get(API.USER.ME, configWithToken(access))
-    .then(resultMiddleware)
-    .catch(errorMiddleware)
-    .then(authMeTransform);
diff --git a/src/redux/auth/reducer.ts b/src/redux/auth/reducer.ts
index 766b0b94..fd7acc69 100644
--- a/src/redux/auth/reducer.ts
+++ b/src/redux/auth/reducer.ts
@@ -1,14 +1,14 @@
-import {EMPTY_TOKEN, EMPTY_USER, AUTH_USER_ACTIONS} from "~/redux/auth/constants";
+import { EMPTY_USER } from "~/redux/auth/constants";
 import { createReducer } from "~/utils/reducer";
-import {IAuthState} from "~/redux/auth/types";
-import {AUTH_USER_HANDLERS} from "~/redux/auth/handlers";
+import { IAuthState } from "~/redux/auth/types";
+import { AUTH_USER_HANDLERS } from "~/redux/auth/handlers";
 
 const HANDLERS = {
   ...AUTH_USER_HANDLERS,
 };
 
 const INITIAL_STATE: IAuthState = {
-  token: { ...EMPTY_TOKEN },
+  token: null,
   user: { ...EMPTY_USER },
   login: {
     error: null,
diff --git a/src/redux/auth/sagas.ts b/src/redux/auth/sagas.ts
index 2c2cab24..e969ee89 100644
--- a/src/redux/auth/sagas.ts
+++ b/src/redux/auth/sagas.ts
@@ -1,26 +1,43 @@
-import {call, put, takeLatest } from 'redux-saga/effects';
+import { call, put, takeLatest, select } from 'redux-saga/effects';
 import { SagaIterator } from 'redux-saga';
-import {AUTH_USER_ACTIONS} from "~/redux/auth/constants";
+import { AUTH_USER_ACTIONS } from "~/redux/auth/constants";
 import * as ActionCreators from '~/redux/auth/actions';
-import {authSetToken, userSetLoginError} from "~/redux/auth/actions";
-import {apiUserLogin, getAuthSelf} from "~/redux/auth/api";
-import {modalSetShown} from "~/redux/modal/actions";
+import { authSetToken, userSetLoginError, authSetUser } from "~/redux/auth/actions";
+import { apiUserLogin } from "~/redux/auth/api";
+import { modalSetShown, modalShowDialog } from "~/redux/modal/actions";
+import { selectToken } from './selectors';
+import { push } from 'connected-react-router';
+import { URLS } from '~/constants/urls';
+import { DIALOGS } from '../modal/constants';
+import { IResultWithStatus } from '../types';
+import { IToken, IUser } from './types';
+
+export function* reqWrapper(requestAction, props = {}): ReturnType<typeof requestAction> {
+  const { access } = yield select(selectToken);
+
+  const result = yield call(requestAction, { access, ...props });
+
+  if (result && result.status === 401) {
+    yield put(push(URLS.BASE));
+    yield put(modalShowDialog(DIALOGS.LOGIN));
+
+    return result;
+  }
+
+  return result;
+}
 
 function* sendLoginRequestSaga({ username, password }: ReturnType<typeof ActionCreators.userSendLoginRequest>): SagaIterator {
   if (!username || !password) return;
 
-  const { error, data: { access, refresh }} = yield call(apiUserLogin, { username, password });
+  const { error, data: { token, user } }: IResultWithStatus<{ token: string, user: IUser }> = yield call(apiUserLogin, { username, password });
 
-  console.log({ access, refresh, error });
+  console.log({ token, error });
 
   if (error) return yield put(userSetLoginError(error));
 
-  yield put(authSetToken({ access, refresh }));
-
-  const info = yield call(getAuthSelf); // todo: reqWrapper here
-
-  // todo: get /auth/me
-
+  yield put(authSetToken(token));
+  yield put(authSetUser(user));
   yield put(modalSetShown(false));
 }
 
diff --git a/src/redux/auth/selectors.ts b/src/redux/auth/selectors.ts
index 98570e98..483d874f 100644
--- a/src/redux/auth/selectors.ts
+++ b/src/redux/auth/selectors.ts
@@ -1,4 +1,5 @@
-import {IState} from "~/redux/store";
+import { IState } from "~/redux/store";
 
 export const selectUser = (state: IState): IState['auth']['user'] => state.auth.user;
+export const selectToken = (state: IState): IState['auth']['token'] => state.auth.token;
 export const selectAuthLogin = (state: IState): IState['auth']['login'] => state.auth.login;
diff --git a/src/redux/auth/transforms.ts b/src/redux/auth/transforms.ts
index 43ee722b..1ee063e6 100644
--- a/src/redux/auth/transforms.ts
+++ b/src/redux/auth/transforms.ts
@@ -1,8 +1,9 @@
-import {IResultWithStatus} from "~/redux/types";
+import { IResultWithStatus } from "~/redux/types";
+import { HTTP_RESPONSES } from "~/utils/api";
 
-export const userLoginTransform = ({ status, data,error }: IResultWithStatus<any>): IResultWithStatus<any> => {
-  switch(true) {
-    case status === 401 || !data.access || data.refresh:
+export const userLoginTransform = ({ status, data, error }: IResultWithStatus<any>): IResultWithStatus<any> => {
+  switch (true) {
+    case (status === HTTP_RESPONSES.UNAUTHORIZED || !data.token) && status !== HTTP_RESPONSES.CONNECTION_REFUSED:
       return { status, data, error: 'Пользователь не найден' };
 
     case status === 200:
@@ -11,17 +12,4 @@ export const userLoginTransform = ({ status, data,error }: IResultWithStatus<any
     default:
       return { status, data, error: error || 'Неизвестная ошибка' };
   }
-};
-
-export const authMeTransform = ({ status, data,error }: IResultWithStatus<any>): IResultWithStatus<any> => {
-  switch(true) {
-    case status === 401:
-      return { status, data, error: 'Пользователь не авторизован' };
-
-    case status === 200:
-      return { status, data, error: null };
-
-    default:
-      return { status, data, error: error || 'Неизвестная ошибка' };
-  }
-};
+};
\ No newline at end of file
diff --git a/src/redux/auth/types.ts b/src/redux/auth/types.ts
index eb07a06e..03a5cfca 100644
--- a/src/redux/auth/types.ts
+++ b/src/redux/auth/types.ts
@@ -17,7 +17,7 @@ export interface IUser {
 
 export type IAuthState = Readonly<{
   user: IUser;
-  token: IToken;
+  token: string;
 
   login: {
     error: string;
diff --git a/src/redux/node/reducer.ts b/src/redux/node/reducer.ts
new file mode 100644
index 00000000..ed59aab9
--- /dev/null
+++ b/src/redux/node/reducer.ts
@@ -0,0 +1,15 @@
+import { createReducer } from "~/utils/reducer";
+
+export type INodeState = {
+  is_loading: boolean;
+}
+
+const HANDLERS = {
+
+};
+
+const INITIAL_STATE: INodeState = {
+  is_loading: false,
+};
+
+export default createReducer(INITIAL_STATE, HANDLERS);
\ No newline at end of file
diff --git a/src/utils/api/index.ts b/src/utils/api/index.ts
index eaad04e4..051b8457 100644
--- a/src/utils/api/index.ts
+++ b/src/utils/api/index.ts
@@ -38,9 +38,9 @@ export const errorMiddleware = <T extends any>(debug): IResultWithStatus<T> =>
     ? debug.response
     : {
       status: HTTP_RESPONSES.CONNECTION_REFUSED,
-      data: null,
+      data: {},
       debug,
-      error: 'Network_Disconnected',
+      error: 'Ошибка сети',
     };
 
 export const configWithToken = (