diff --git a/app/src/App.vue b/app/src/App.vue index 30dd406..29abacf 100644 --- a/app/src/App.vue +++ b/app/src/App.vue @@ -6,4 +6,27 @@ diff --git a/app/src/common/EventBus.ts b/app/src/common/EventBus.ts new file mode 100644 index 0000000..c0d5f9a --- /dev/null +++ b/app/src/common/EventBus.ts @@ -0,0 +1,13 @@ +const eventBus = { + on(event: string, callback) { + document.addEventListener(event, (e) => callback(e.detail)); + }, + dispatch(event: string, data) { + document.dispatchEvent(new CustomEvent(event, { detail: data })); + }, + remove(event: string, callback) { + document.removeEventListener(event, callback); + }, +}; + +export default eventBus; diff --git a/app/src/main.ts b/app/src/main.ts index 1c2cc67..195347d 100644 --- a/app/src/main.ts +++ b/app/src/main.ts @@ -1,7 +1,7 @@ import { createApp } from 'vue' import App from './App.vue' import router from './router'; -import Vuex from 'vuex' +import setupInterceptors from './services/SetupInterceptors'; import { IonicVue } from '@ionic/vue'; @@ -37,6 +37,8 @@ import './theme/variables.css'; import store from './store'; +setupInterceptors(store); + const app = createApp(App) .use(store) .use(IonicVue) diff --git a/app/src/services/Api.ts b/app/src/services/Api.ts new file mode 100644 index 0000000..775a4f0 --- /dev/null +++ b/app/src/services/Api.ts @@ -0,0 +1,10 @@ +import axios from "axios"; + +const instance = axios.create({ + baseURL: "http://localhost:8080/api", + headers: { + "Content-Type": "application/json", + }, +}); + +export default instance; diff --git a/app/src/services/AuthService.ts b/app/src/services/AuthService.ts index 0931541..6a6a036 100644 --- a/app/src/services/AuthService.ts +++ b/app/src/services/AuthService.ts @@ -1,17 +1,16 @@ -import axios from 'axios'; - -const API_URL = 'http://localhost:8080/api/auth/'; +import api from './Api'; +import TokenService from './TokenService'; class AuthService { public login(email: string, password: string) { - return axios - .post(API_URL + 'login', { + return api + .post('/auth/login', { email, password }) .then(response => { if (response.data.accessToken) { - localStorage.setItem('user', JSON.stringify(response.data)); + TokenService.setUser(response.data); } return response.data; @@ -19,16 +18,15 @@ class AuthService { } public logout() { - localStorage.removeItem('user'); + TokenService.removeUser(); } - public register(username: string, email: string, password: string) { - return axios.post(API_URL + 'register', { - username, + public register(email: string, password: string) { + return api.post('/auth/register', { email, password }); } } -export default new AuthService(); \ No newline at end of file +export default new AuthService(); diff --git a/app/src/services/PitchTypeService.ts b/app/src/services/PitchTypeService.ts index 4bc348b..f0c3557 100644 --- a/app/src/services/PitchTypeService.ts +++ b/app/src/services/PitchTypeService.ts @@ -1,12 +1,5 @@ import PitchType from "@/types/PitchType"; -import axios, {AxiosInstance} from "axios"; - -const apiClient: AxiosInstance = axios.create({ - baseURL: "http://localhost:8080/api", - headers: { - "Content-type": "application/json", - }, -}); +import api from './Api'; class PitchTypeService { private static instance: PitchTypeService; @@ -22,12 +15,12 @@ class PitchTypeService { } async getAllPitchTypes(): Promise { - const users = await apiClient.get('/pitch_types'); + const users = await api.get('/pitch_types'); return users.data; } async getPitchType(id: number): Promise { - const pitchType = await apiClient.get(`/pitch_types/${id}`); + const pitchType = await api.get(`/pitch_types/${id}`); return pitchType.data; // if (pitcher !== undefined) { // return pitcher; @@ -37,4 +30,4 @@ class PitchTypeService { } } -export const pitchTypeService = PitchTypeService.getInstance(); \ No newline at end of file +export const pitchTypeService = PitchTypeService.getInstance(); diff --git a/app/src/services/PitcherService.ts b/app/src/services/PitcherService.ts index 186c694..87fa4fc 100644 --- a/app/src/services/PitcherService.ts +++ b/app/src/services/PitcherService.ts @@ -1,12 +1,6 @@ import Pitcher from "@/types/Pitcher"; -import axios, { AxiosInstance } from "axios"; +import api from './Api'; -const apiClient: AxiosInstance = axios.create({ - baseURL: "http://localhost:8080/api", - headers: { - "Content-type": "application/json", - }, -}); class PitcherService { private static instance: PitcherService; @@ -21,12 +15,12 @@ class PitcherService { } async getAllPitchers(): Promise { - const users = await apiClient.get('/users'); + const users = await api.get('/users'); return users.data; } async getPitcher(id: number): Promise { - const pitcher = await apiClient.get(`/users/${id}`); + const pitcher = await api.get(`/users/${id}`); return pitcher.data; // const pitcher = pitchers.get(id); // if (pitcher !== undefined) { @@ -37,4 +31,4 @@ class PitcherService { } } -export const pitcherService = PitcherService.getInstance(); \ No newline at end of file +export const pitcherService = PitcherService.getInstance(); diff --git a/app/src/services/SetupInterceptors.ts b/app/src/services/SetupInterceptors.ts new file mode 100644 index 0000000..fd8efef --- /dev/null +++ b/app/src/services/SetupInterceptors.ts @@ -0,0 +1,55 @@ +import axiosInstance from "./Api"; +import TokenService from "./TokenService"; +import { Store } from 'vuex'; +import {RootState} from '@/store'; + +const setup = (store: Store) => { + axiosInstance.interceptors.request.use( + (config) => { + const token = TokenService.getLocalAccessToken(); + if (token) { + // config.headers["Authorization"] = 'Bearer ' + token; // for Spring Boot back-end + config.headers["x-access-token"] = token; // for Node.js Express back-end + } + return config; + }, + (error) => { + return Promise.reject(error); + } + ); + + axiosInstance.interceptors.response.use( + (res) => { + return res; + }, + async (err) => { + const originalConfig = err.config; + + if (originalConfig.url !== "/auth/login" && err.response) { + // Access Token was expired + if (err.response.status === 401 && !originalConfig._retry) { + originalConfig._retry = true; + + try { + const rs = await axiosInstance.post("/auth/refreshtoken", { + refreshToken: TokenService.getLocalRefreshToken(), + }); + + const { accessToken } = rs.data; + + await store.dispatch('auth/refreshToken', accessToken); + TokenService.updateLocalAccessToken(accessToken); + + return axiosInstance(originalConfig); + } catch (_error) { + return Promise.reject(_error); + } + } + } + + return Promise.reject(err); + } + ); +}; + +export default setup; diff --git a/app/src/services/TokenService.ts b/app/src/services/TokenService.ts new file mode 100644 index 0000000..bd2c013 --- /dev/null +++ b/app/src/services/TokenService.ts @@ -0,0 +1,34 @@ +import UserInfo from '@/types/UserInfo'; + +class TokenService { + getLocalRefreshToken() { + const user = JSON.parse(localStorage.getItem("user") || '""'); + return user?.refreshToken; + } + + getLocalAccessToken() { + const user = JSON.parse(localStorage.getItem("user")|| '""'); + return user?.accessToken; + } + + updateLocalAccessToken(token: string) { + const user: UserInfo = JSON.parse(localStorage.getItem("user")|| '""'); + user.accessToken = token; + localStorage.setItem("user", JSON.stringify(user)); + } + + getUser() { + return JSON.parse(localStorage.getItem("user")|| '""'); + } + + setUser(user: UserInfo) { + console.log(JSON.stringify(user)); + localStorage.setItem("user", JSON.stringify(user)); + } + + removeUser() { + localStorage.removeItem("user"); + } +} + +export default new TokenService(); diff --git a/app/src/types/UserInfo.ts b/app/src/types/UserInfo.ts new file mode 100644 index 0000000..55cac7f --- /dev/null +++ b/app/src/types/UserInfo.ts @@ -0,0 +1,7 @@ +export default interface UserInfo { + id: number, + email: string, + roles: string[], + accessToken: string, + refreshToken: string +}