Skip to content

Commit 82e77a4

Browse files
feat: incode mock api authentication (#3)
1 parent 5a64977 commit 82e77a4

35 files changed

+509
-211
lines changed

.gitignore

-2
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@ coverage
1818
/cypress/screenshots/
1919

2020
# Editor directories and files
21-
.vscode/*
22-
!.vscode/extensions.json
2321
.idea
2422
*.suo
2523
*.ntvs*

.prettierrc.json

+1-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,5 @@
44
"tabWidth": 4,
55
"singleQuote": true,
66
"printWidth": 100,
7-
"trailingComma": "none",
8-
"importOrderSeparation": true,
9-
"importOrderSortSpecifiers": true
7+
"trailingComma": "none"
108
}

.vscode/settings.json

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"editor.codeActionsOnSave": {
3+
"source.fixAll.eslint": "explicit",
4+
"source.organizeImports": "explicit"
5+
},
6+
"editor.formatOnSave": true,
7+
"editor.defaultFormatter": "esbenp.prettier-vscode",
8+
"workbench.colorCustomizations": {
9+
"minimap.background": "#00000000",
10+
"scrollbar.shadow": "#00000000"
11+
},
12+
"typescript.tsdk": "node_modules/typescript/lib"
13+
}

src/App.vue

+4-6
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
<script setup lang="ts">
2-
import { RouterView, useRoute } from 'vue-router'
3-
import { ConfigProvider } from 'ant-design-vue'
42
import { theme } from '@/theme'
3+
import { ConfigProvider } from 'ant-design-vue'
4+
import { RouterView, useRoute } from 'vue-router'
55
6+
import AsyncErrorBoundary from '@/components/AsyncErrorBoundary.vue'
7+
import AdminLayout from '@/layouts/admin/index.vue'
68
import AuthLayout from '@/layouts/auth/index.vue'
79
import DefaultLayout from '@/layouts/empty/index.vue'
810
import LandingLayout from '@/layouts/landing-page/index.vue'
9-
import AdminLayout from '@/layouts/admin/index.vue'
1011
import { markRaw, ref, watch } from 'vue'
11-
import Curtain from '@/components/Curtain.vue'
12-
import AsyncErrorBoundary from '@/components/AsyncErrorBoundary.vue'
1312
1413
const layouts: Record<string, typeof DefaultLayout> = {
1514
default: DefaultLayout,
@@ -42,7 +41,6 @@ watch(
4241
<template>
4342
<ConfigProvider :theme="theme">
4443
<AsyncErrorBoundary />
45-
<Curtain />
4644
<Transition>
4745
<component :is="layout">
4846
<RouterView />

src/api/auth/auth.api.ts

+26-9
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,24 @@
11
import { $post } from '@/api'
2+
import { mockApiIAmAdmin, mockApiLogin, mockApiRefreshToken } from '@/helpers/mockApi'
23
import {
34
type LoginRequest,
4-
type LoginResponse,
55
type RefreshTokenRequest,
6-
type RefreshTokenResponse,
76
type RegisterRequest,
87
type RegisterResponse
98
} from './auth.dto'
109

1110
export async function apiLogin(payload: LoginRequest) {
12-
return await $post<LoginResponse>('/auth/login/', payload).then((resp) => {
13-
return resp.data
14-
})
11+
// TODO: Please uncomment the code below and remove the mockApiLogin function
12+
// return await $post<LoginResponse>('/auth/login/', payload).then((resp) => {
13+
// return resp.data
14+
// })
15+
16+
return await mockApiLogin(payload)
1517
}
1618

1719
export async function apiLogout() {
18-
await $post('/auth/logout/')
20+
// TODO: Please uncomment the line below
21+
// await $post('/auth/logout/')
1922
}
2023

2124
export async function apiRegister(payload: RegisterRequest) {
@@ -25,7 +28,21 @@ export async function apiRegister(payload: RegisterRequest) {
2528
}
2629

2730
export async function apiRefreshToken(payload: RefreshTokenRequest) {
28-
return await $post<RefreshTokenResponse>('/auth/refresh/', payload).then((resp) => {
29-
return resp.data
30-
})
31+
// TODO: Please uncomment the code below and remove the mockApiRefreshToken function
32+
// return await $post<RefreshTokenResponse>('/auth/refresh/', payload).then((resp) => {
33+
// return resp.data
34+
// })
35+
36+
return await mockApiRefreshToken(payload)
37+
}
38+
39+
export async function apiIAmAdmin() {
40+
// TODO: add api call to check if user is admin
41+
42+
// Mock implementation
43+
const accessToken = localStorage.getItem('accessToken')
44+
if (!accessToken) {
45+
return Promise.reject({ message: 'Unauthorize' })
46+
}
47+
return await mockApiIAmAdmin(accessToken)
3148
}

src/api/auth/auth.dto.ts

+10-4
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,18 @@ export type LoginRequest = {
55
}
66

77
export type LoginResponse = {
8-
refresh: string
9-
access: string
8+
refreshToken: string
9+
accessToken: string
1010
}
1111

1212
/** REFRESH TOKEN */
1313

1414
export type RefreshTokenRequest = {
15-
refresh: string
15+
refreshToken: string
1616
}
1717

1818
export type RefreshTokenResponse = {
19-
access: string
19+
accessToken: string
2020
}
2121

2222
/** REGISTER */
@@ -30,3 +30,9 @@ export type RegisterRequest = {
3030
export type RegisterResponse = {
3131
//
3232
}
33+
34+
/** I AM ADMIN */
35+
36+
export type IAmAdminResponse = {
37+
isAdmin: boolean
38+
}

src/api/auth/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './auth.api'
2+
export * from './auth.dto'

src/api/axios.client.ts

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
2+
import axios, { AxiosError, type AxiosRequestConfig, type AxiosResponse } from 'axios'
3+
4+
const axiosClient = axios.create({
5+
baseURL: import.meta.env.VITE_API_URL
6+
})
7+
8+
axiosClient.interceptors.request.use((config) => {
9+
config.headers['Content-Type'] = 'application/json'
10+
config.headers['Access-Control-Allow-Origin'] = '*'
11+
// X-CSRFToken: FGWp2aF83aGv8UNqTUzKom4RwMfsYo5kSqzdHahcsi495vgsyYnzBEgY9OohXcWr
12+
13+
const accessToken = localStorage.getItem('accessToken')
14+
if (accessToken) {
15+
config.headers.Authorization = `Bearer ${accessToken}`
16+
}
17+
18+
return config
19+
})
20+
21+
axiosClient.interceptors.response.use(
22+
(response) => response,
23+
async (error: AxiosError) => {
24+
const resp = error.response as any
25+
const respErrorCode = resp?.status ?? 500
26+
if (respErrorCode === 401) {
27+
localStorage.removeItem('accessToken')
28+
localStorage.removeItem('refreshToken')
29+
window.location.href = '/login'
30+
}
31+
const respErrorMessage = resp?.data?.error ?? resp?.data?.message ?? 'UNKNOWN_ERROR'
32+
33+
throw new Error(respErrorMessage)
34+
}
35+
)
36+
37+
function $post<T = any, R = AxiosResponse<T>, D = any>(
38+
url: string,
39+
data?: D,
40+
config?: AxiosRequestConfig<D>
41+
): Promise<R> {
42+
try {
43+
return axiosClient.post<T, R>(url, data, config)
44+
} catch (error) {
45+
return Promise.reject(error)
46+
}
47+
}
48+
49+
function $get<T = any, R = AxiosResponse<T>, D = any>(
50+
url: string,
51+
config?: AxiosRequestConfig<D>
52+
): Promise<R> {
53+
try {
54+
return axiosClient.get<T, R>(url, config)
55+
} catch (error) {
56+
return Promise.reject(error)
57+
}
58+
}
59+
60+
function $put<T = any, R = AxiosResponse<T>, D = any>(
61+
url: string,
62+
data?: D,
63+
config?: AxiosRequestConfig<D>
64+
): Promise<R> {
65+
try {
66+
return axiosClient.put<T, R>(url, data, config)
67+
} catch (error) {
68+
return Promise.reject(error)
69+
}
70+
}
71+
72+
function $patch<T = any, R = AxiosResponse<T>, D = any>(
73+
url: string,
74+
data?: D,
75+
config?: AxiosRequestConfig<D>
76+
): Promise<R> {
77+
try {
78+
return axiosClient.patch<T, R>(url, data, config)
79+
} catch (error) {
80+
return Promise.reject(error)
81+
}
82+
}
83+
84+
function $delete<T = any, R = AxiosResponse<T>, D = any>(
85+
url: string,
86+
config?: AxiosRequestConfig<D>
87+
): Promise<R> {
88+
try {
89+
return axiosClient.delete<T, R>(url, config)
90+
} catch (error) {
91+
return Promise.reject(error)
92+
}
93+
}
94+
95+
export { $delete, $get, $patch, $post, $put }

src/api/index.ts

+2-94
Original file line numberDiff line numberDiff line change
@@ -1,95 +1,3 @@
1-
/* eslint-disable @typescript-eslint/no-explicit-any */
2-
import axios, { AxiosError, type AxiosRequestConfig, type AxiosResponse } from 'axios'
1+
export * from './axios.client'
32

4-
const axiosClient = axios.create({
5-
baseURL: import.meta.env.VITE_API_URL
6-
})
7-
8-
axiosClient.interceptors.request.use((config) => {
9-
config.headers['Content-Type'] = 'application/json'
10-
config.headers['Access-Control-Allow-Origin'] = '*'
11-
// X-CSRFToken: FGWp2aF83aGv8UNqTUzKom4RwMfsYo5kSqzdHahcsi495vgsyYnzBEgY9OohXcWr
12-
13-
const accessToken = localStorage.getItem('accessToken')
14-
if (accessToken) {
15-
config.headers.Authorization = `Bearer ${accessToken}`
16-
}
17-
18-
return config
19-
})
20-
21-
axiosClient.interceptors.response.use(
22-
(response) => response,
23-
async (error: AxiosError) => {
24-
const resp = error.response as any
25-
const respErrorCode = resp?.status ?? 500
26-
if (respErrorCode === 401) {
27-
localStorage.removeItem('accessToken')
28-
localStorage.removeItem('refreshToken')
29-
window.location.href = '/login'
30-
}
31-
const respErrorMessage = resp?.data?.error ?? resp?.data?.message ?? 'UNKNOWN_ERROR'
32-
33-
throw new Error(respErrorMessage)
34-
}
35-
)
36-
37-
function $post<T = any, R = AxiosResponse<T>, D = any>(
38-
url: string,
39-
data?: D,
40-
config?: AxiosRequestConfig<D>
41-
): Promise<R> {
42-
try {
43-
return axiosClient.post<T, R>(url, data, config)
44-
} catch (error) {
45-
return Promise.reject(error)
46-
}
47-
}
48-
49-
function $get<T = any, R = AxiosResponse<T>, D = any>(
50-
url: string,
51-
config?: AxiosRequestConfig<D>
52-
): Promise<R> {
53-
try {
54-
return axiosClient.get<T, R>(url, config)
55-
} catch (error) {
56-
return Promise.reject(error)
57-
}
58-
}
59-
60-
function $put<T = any, R = AxiosResponse<T>, D = any>(
61-
url: string,
62-
data?: D,
63-
config?: AxiosRequestConfig<D>
64-
): Promise<R> {
65-
try {
66-
return axiosClient.put<T, R>(url, data, config)
67-
} catch (error) {
68-
return Promise.reject(error)
69-
}
70-
}
71-
72-
function $patch<T = any, R = AxiosResponse<T>, D = any>(
73-
url: string,
74-
data?: D,
75-
config?: AxiosRequestConfig<D>
76-
): Promise<R> {
77-
try {
78-
return axiosClient.patch<T, R>(url, data, config)
79-
} catch (error) {
80-
return Promise.reject(error)
81-
}
82-
}
83-
84-
function $delete<T = any, R = AxiosResponse<T>, D = any>(
85-
url: string,
86-
config?: AxiosRequestConfig<D>
87-
): Promise<R> {
88-
try {
89-
return axiosClient.delete<T, R>(url, config)
90-
} catch (error) {
91-
return Promise.reject(error)
92-
}
93-
}
94-
95-
export { $get, $post, $put, $patch, $delete }
3+
export * from './auth'

src/assets/styles/index.less

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@
77
html,
88
html body {
99
font-family: 'Montserrat', 'Roboto', sans-serif !important;
10-
}
10+
}

src/components/AsyncErrorBoundary.vue

+2-3
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@
44
</template>
55

66
<script setup lang="ts">
7-
import { onUnmounted } from 'vue'
8-
import { v4 as uuid } from 'uuid'
97
import { notification } from 'ant-design-vue'
10-
import { onMounted } from 'vue'
8+
import { v4 as uuid } from 'uuid'
9+
import { onMounted, onUnmounted } from 'vue'
1110
const [api, contextHolder] = notification.useNotification()
1211
1312
/**

src/components/Container.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script setup lang="ts">
2-
import { clsx, type ClassValue } from '@/utils/clsx'
2+
import { clsx, type ClassValue } from '@/utils'
33
44
defineProps({
55
className: {

0 commit comments

Comments
 (0)