From c1bacacb4972e2985376963d0a92c2376fe9b6cd Mon Sep 17 00:00:00 2001 From: Ardeman Date: Tue, 25 Feb 2025 08:51:08 +0800 Subject: [PATCH] feat: add cookie management and HTTP client for user and admin authentication --- app/configs/storages.ts | 7 ++++++ app/libs/cookie.server.ts | 13 ++++++++++ app/libs/http-client.ts | 31 ++++++++++++++++++++++++ app/libs/logout-header.server.ts | 11 +++++++++ eslint.config.mjs | 28 ++++++++++++---------- package.json | 2 ++ pnpm-lock.yaml | 41 ++++++++++++++++++++++++++++++++ 7 files changed, 120 insertions(+), 13 deletions(-) create mode 100644 app/configs/storages.ts create mode 100644 app/libs/cookie.server.ts create mode 100644 app/libs/http-client.ts create mode 100644 app/libs/logout-header.server.ts diff --git a/app/configs/storages.ts b/app/configs/storages.ts new file mode 100644 index 0000000..7e832ae --- /dev/null +++ b/app/configs/storages.ts @@ -0,0 +1,7 @@ +export const USER_COOKIES = { + token: '__lg-usr-tkn', +} + +export const ADMIN_COOKIES = { + token: '__lg-adm-tkn', +} diff --git a/app/libs/cookie.server.ts b/app/libs/cookie.server.ts new file mode 100644 index 0000000..8b6826c --- /dev/null +++ b/app/libs/cookie.server.ts @@ -0,0 +1,13 @@ +import { createCookie } from 'react-router' + +import { ADMIN_COOKIES, USER_COOKIES } from '~/configs/storages' + +export const userTokenCookie = createCookie(USER_COOKIES.token, { + secure: process.env.NODE_ENV === 'production', + path: '/news', +}) + +export const adminTokenCookie = createCookie(ADMIN_COOKIES.token, { + secure: process.env.NODE_ENV === 'production', + path: '/admin', +}) diff --git a/app/libs/http-client.ts b/app/libs/http-client.ts new file mode 100644 index 0000000..0257fb4 --- /dev/null +++ b/app/libs/http-client.ts @@ -0,0 +1,31 @@ +import xior, { merge } from 'xior' + +const baseURL = import.meta.env.VITE_API_URL +const HttpClient = (token?: string) => { + const instance = xior.create({ + baseURL, + }) + instance.interceptors.request.use((config) => { + // eslint-disable-next-line no-console + console.info(`🚀requesting ${config.url}`) + + return merge(config, { + headers: { + ...(token && { Authorization: `Bearer ${token}` }), + }, + }) + }) + + instance.interceptors.response.use( + (response) => { + return response + }, + (error) => { + return Promise.reject(error) + }, + ) + + return instance +} + +export default HttpClient diff --git a/app/libs/logout-header.server.ts b/app/libs/logout-header.server.ts new file mode 100644 index 0000000..73dff66 --- /dev/null +++ b/app/libs/logout-header.server.ts @@ -0,0 +1,11 @@ +import { USER_COOKIES } from '~/configs/storages' + +export const setUserLogoutHeaders = () => { + const responseHeaders = new Headers() + responseHeaders.append( + 'Set-Cookie', + `${USER_COOKIES.token}=; Path=/news; Max-Age=0`, + ) + + return responseHeaders +} diff --git a/eslint.config.mjs b/eslint.config.mjs index 974af76..f878c6a 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -8,6 +8,7 @@ import tsEslintParser from '@typescript-eslint/parser' import eslintPluginUnicorn from 'eslint-plugin-unicorn' import reactPlugin from 'eslint-plugin-react' import pluginQuery from '@tanstack/eslint-plugin-query' +import unusedImports from 'eslint-plugin-unused-imports' export default tseslint.config( { ignores: ['dist', 'node_modules', '.react-router'] }, @@ -28,6 +29,7 @@ export default tseslint.config( plugins: { 'react-hooks': reactHooks, react: reactPlugin, + 'unused-imports': unusedImports, }, languageOptions: { parser: tsEslintParser, @@ -47,18 +49,7 @@ export default tseslint.config( ...reactHooks.configs.recommended.rules, ...tseslint.configs.recommended.rules, ...reactPlugin.configs.recommended.rules, - '@typescript-eslint/no-unused-vars': [ - 'error', - { - args: 'all', - argsIgnorePattern: '^_', - caughtErrors: 'all', - caughtErrorsIgnorePattern: '^_', - destructuredArrayIgnorePattern: '^_', - varsIgnorePattern: '^_', - ignoreRestSiblings: true, - }, - ], + '@typescript-eslint/no-unused-vars': 'off', 'unicorn/filename-case': [ 'error', { @@ -90,7 +81,8 @@ export default tseslint.config( '~/layouts/*/*/*/*', '~/schemas/*/*/*', '~/types/*/*', - '~/utils/*/**', + '~/utils/*/*', + '~/libs/*/*', ], }, ], @@ -125,6 +117,16 @@ export default tseslint.config( ], }, ], + 'unused-imports/no-unused-imports': 'error', + 'unused-imports/no-unused-vars': [ + 'error', + { + vars: 'all', + varsIgnorePattern: '^_', + args: 'after-used', + argsIgnorePattern: '^_', + }, + ], 'react/react-in-jsx-scope': 'off', 'react/jsx-key': [ 'error', diff --git a/package.json b/package.json index 7f117de..4d62e7f 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "react-hook-form": "^7.54.2", "react-router": "^7.1.3", "tailwind-merge": "^3.0.1", + "xior": "^0.6.3", "zod": "^3.24.2" }, "devDependencies": { @@ -56,6 +57,7 @@ "eslint-plugin-react": "^7.37.4", "eslint-plugin-react-hooks": "^5.1.0", "eslint-plugin-unicorn": "^56.0.1", + "eslint-plugin-unused-imports": "^4.1.4", "globals": "^15.14.0", "husky": "^9.1.7", "knip": "^5.43.6", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2a699d2..cd8b532 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -65,6 +65,9 @@ importers: tailwind-merge: specifier: ^3.0.1 version: 3.0.1 + xior: + specifier: ^0.6.3 + version: 0.6.3 zod: specifier: ^3.24.2 version: 3.24.2 @@ -129,6 +132,9 @@ importers: eslint-plugin-unicorn: specifier: ^56.0.1 version: 56.0.1(eslint@8.57.1) + eslint-plugin-unused-imports: + specifier: ^4.1.4 + version: 4.1.4(@typescript-eslint/eslint-plugin@8.22.0(@typescript-eslint/parser@8.22.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1) globals: specifier: ^15.14.0 version: 15.14.0 @@ -2229,6 +2235,15 @@ packages: peerDependencies: eslint: '>=8.56.0' + eslint-plugin-unused-imports@4.1.4: + resolution: {integrity: sha512-YptD6IzQjDardkl0POxnnRBhU1OEePMV0nd6siHaRBbd+lyh6NAhFEobiznKU7kTsSsDeSD62Pe7kAM1b7dAZQ==} + peerDependencies: + '@typescript-eslint/eslint-plugin': ^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0 + eslint: ^9.0.0 || ^8.0.0 + peerDependenciesMeta: + '@typescript-eslint/eslint-plugin': + optional: true + eslint-scope@7.2.2: resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -3927,6 +3942,10 @@ packages: through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + tiny-lru@11.2.11: + resolution: {integrity: sha512-27BIW0dIWTYYoWNnqSmoNMKe5WIbkXsc0xaCQHd3/3xT2XMuMJrzHdrO9QBFR14emBz1Bu0dOAs2sCBBrvgPQA==} + engines: {node: '>=12'} + tinyexec@0.3.2: resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} @@ -3944,6 +3963,10 @@ packages: peerDependencies: typescript: '>=4.8.4' + ts-deepmerge@7.0.2: + resolution: {integrity: sha512-akcpDTPuez4xzULo5NwuoKwYRtjQJ9eoNfBACiBMaXwNAx7B1PKfe5wqUFJuW5uKzQ68YjDFwPaWHDG1KnFGsA==} + engines: {node: '>=14.13.1'} + tsconfck@3.1.4: resolution: {integrity: sha512-kdqWFGVJqe+KGYvlSO9NIaWn9jT1Ny4oKVzAJsKii5eoE9snzTJzL4+MMVOMn+fikWGFmKEylcXL710V/kIPJQ==} engines: {node: ^18 || >=20} @@ -4191,6 +4214,9 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + xior@0.6.3: + resolution: {integrity: sha512-WxDMGk7W2duFoCS0M59Pll/BIGfWiadZp4MMEY0/56K+3Vz400DUTiEZLpuaVcSnv+pCSz05MJz8kohn8wivhg==} + xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} @@ -6514,6 +6540,12 @@ snapshots: semver: 7.7.0 strip-indent: 3.0.0 + eslint-plugin-unused-imports@4.1.4(@typescript-eslint/eslint-plugin@8.22.0(@typescript-eslint/parser@8.22.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1): + dependencies: + eslint: 8.57.1 + optionalDependencies: + '@typescript-eslint/eslint-plugin': 8.22.0(@typescript-eslint/parser@8.22.0(eslint@8.57.1)(typescript@5.7.3))(eslint@8.57.1)(typescript@5.7.3) + eslint-scope@7.2.2: dependencies: esrecurse: 4.3.0 @@ -8260,6 +8292,8 @@ snapshots: through@2.3.8: {} + tiny-lru@11.2.11: {} + tinyexec@0.3.2: {} to-regex-range@5.0.1: @@ -8272,6 +8306,8 @@ snapshots: dependencies: typescript: 5.7.3 + ts-deepmerge@7.0.2: {} + tsconfck@3.1.4(typescript@5.7.3): optionalDependencies: typescript: 5.7.3 @@ -8528,6 +8564,11 @@ snapshots: wrappy@1.0.2: {} + xior@0.6.3: + dependencies: + tiny-lru: 11.2.11 + ts-deepmerge: 7.0.2 + xtend@4.0.2: {} y18n@5.0.8: {}