diff --git a/app/src/App.vue b/app/src/App.vue index ca96f89..56cb886 100644 --- a/app/src/App.vue +++ b/app/src/App.vue @@ -11,7 +11,7 @@ const store = useStore(); const logout = () => { store.dispatch('auth/logout'); - router.push('/login'); + router.push({path: '/login'}); } const isOnline = ref(navigator.onLine); @@ -23,7 +23,7 @@ onMounted(() => { window.addEventListener("offline", () => isOnline.value = false); if (isOnline.value) { - router.push("/login"); + router.push({path: '/login'}); } else { // checkStoredUserData(); } diff --git a/app/src/common/EventBus.ts b/app/src/common/EventBus.ts index d403416..3a0ff01 100644 --- a/app/src/common/EventBus.ts +++ b/app/src/common/EventBus.ts @@ -15,8 +15,9 @@ class EventBus { document.addEventListener(type, listener as EventListener, options); } - dispatch(event: T): void { - document.dispatchEvent(event); + // dispatch(event: T): void { + dispatch(event: string, data: any = null): void { + document.dispatchEvent(new CustomEvent(event, { detail: data })); } remove(type: string, listener: (event: T) => void, options?: boolean | EventListenerOptions): void { diff --git a/app/src/data/pitchers.ts b/app/src/data/pitchers.ts deleted file mode 100644 index 71c929f..0000000 --- a/app/src/data/pitchers.ts +++ /dev/null @@ -1,93 +0,0 @@ -import User from "@/types/User"; - -export const pitchers: Map = new Map([ - [ - 1, - { - id: 1, - firstName: "Nolan", - lastName: "Ryan", - dateOfBirth: new Date("1947-01-31") - } - ], - [ - 2, - { - id: 2, - firstName: "Sandy", - lastName: "Koufax", - dateOfBirth: new Date("1935-12-30") - } - ], - [ - 3, - { - id: 3, - firstName: "Pedro", - lastName: "Martinez", - dateOfBirth: new Date("1971-10-25") - } - ], - [ - 4, - { - id: 4, - firstName: "Randy", - lastName: "Johnson", - dateOfBirth: new Date("1963-09-10") - } - ], - [ - 5, - { - id: 5, - firstName: "Greg", - lastName: "Maddux", - dateOfBirth: new Date("1966-04-14") - } - ], - [ - 6, - { - id: 6, - firstName: "Bob", - lastName: "Gibson", - dateOfBirth: new Date("1935-11-09") - } - ], - [ - 7, - { - id: 7, - firstName: "Tom", - lastName: "Seaver", - dateOfBirth: new Date("1944-11-17") - } - ], - [ - 8, - { - id: 8, - firstName: "Roger", - lastName: "Clemens", - dateOfBirth: new Date("1962-08-04") - } - ], - [ - 9, - { - id: 9, - firstName: "Walter", - lastName: "Johnson", - dateOfBirth: new Date("1887-11-06") - } - ], - [ - 10, - { - id: 10, - firstName: "Clayton", - lastName: "Kershaw", - dateOfBirth: new Date("1988-03-19") - } - ]]); \ No newline at end of file diff --git a/app/src/router/index.ts b/app/src/router/index.ts index 3a37c39..c4edd58 100644 --- a/app/src/router/index.ts +++ b/app/src/router/index.ts @@ -8,6 +8,7 @@ import BullpenView from "@/views/BullpenView.vue"; import BullpenSummaryView from "@/views/BullpenSummaryView.vue"; import SetupView from '@/views/SetupView.vue'; import backendService from '@/services/BackendService' +import BullpenListView from "@/views/BullpenListView.vue"; const routes: Array = [ { path: '/', redirect: '/login' }, @@ -16,6 +17,7 @@ const routes: Array = [ { path: '/home', component: HomeView }, { path: '/pitchers', component: PitcherList }, { path: '/bullpen', component: BullpenView }, + { path: '/stats', component: BullpenListView }, { path: '/summary', component: BullpenSummaryView } ] diff --git a/app/src/services/Api.ts b/app/src/services/Api.ts index 25ab1c0..f3ad431 100644 --- a/app/src/services/Api.ts +++ b/app/src/services/Api.ts @@ -1,6 +1,7 @@ import axios from "axios"; +import backendService from '@/services/BackendService' -const server = JSON.parse(localStorage.getItem("server") || '""'); +const server = backendService.getServer() const instance = axios.create({ baseURL: `${server.protocol}://${server.host}:${server.port}/api`, diff --git a/app/src/services/ApiService.ts b/app/src/services/ApiService.ts new file mode 100644 index 0000000..4729456 --- /dev/null +++ b/app/src/services/ApiService.ts @@ -0,0 +1,52 @@ +import api from "@/services/Api"; +import authHeader from "@/services/AuthHeader"; +import {AxiosError, AxiosResponse} from "axios"; +import EventBus from "@/common/EventBus"; + +export class ApiService { + protected name: string; + + constructor(name: string) { + this.name = name; + } + + public save(entity: Type): Promise { + return api + .post(`${this.name}`, entity) + .then(response => { + return response.data; + }, (error) => { + this.handleExpiredToken(error); + return Promise.reject(error); + }); + } + + public fetchAll(): Promise { + return api + .get(`/${this.name}`, { headers: authHeader() }) + .then((response: AxiosResponse) => { + return response.data; + }, (error) => { + this.handleExpiredToken(error); + return Promise.reject(error); + }); + } + + public fetchById(id: number): Promise { + return api + .get(`/${this.name}/${id}`, { headers: authHeader() }) + .then((response: AxiosResponse) => { + return response.data; + }, (error: AxiosError) => { + this.handleExpiredToken(error); + return Promise.reject(error); + }); + } + + protected handleExpiredToken(error: any) { + if (error.response && error.response.status === 403) { + console.log('Refresh token expired. Logout and redirect to login page.'); + EventBus.dispatch("logout"); + } + } +} \ No newline at end of file diff --git a/app/src/services/AuthService.ts b/app/src/services/AuthService.ts index 1a9e2cf..4f87a6d 100644 --- a/app/src/services/AuthService.ts +++ b/app/src/services/AuthService.ts @@ -2,7 +2,7 @@ import api from './Api'; import TokenService from './TokenService'; class AuthService { - public login(email: string, password: string) { + public async login(email: string, password: string) { return api .post('/auth/login', { email, @@ -17,7 +17,7 @@ class AuthService { }); } - public logout(): Promise { + public async logout(): Promise { TokenService.removeUser(); return Promise.resolve(); diff --git a/app/src/services/BackendService.ts b/app/src/services/BackendService.ts index c9f9e63..aa787d5 100644 --- a/app/src/services/BackendService.ts +++ b/app/src/services/BackendService.ts @@ -5,7 +5,8 @@ class BackendService { return localStorage.getItem("server") !== null; } getServer(): Server { - return JSON.parse(localStorage.getItem("server") || '{"protocol":"https","host":"localhost","port":8124}'); + return JSON.parse(localStorage.getItem("server") || '{"protocol":"http","host":"localhost","port":8080}'); + // return JSON.parse(localStorage.getItem("server") || '{"protocol":"https","host":"bullpen-api.palaeomatiker.home64.de","port":443}'); } updateServer(server: Server): void { localStorage.setItem("server", JSON.stringify(server)); diff --git a/app/src/services/BullpenSessionService.ts b/app/src/services/BullpenSessionService.ts index 8fc5914..fa42f20 100644 --- a/app/src/services/BullpenSessionService.ts +++ b/app/src/services/BullpenSessionService.ts @@ -1,9 +1,15 @@ import Pitch from "@/types/Pitch"; import User from "@/types/User"; import Bullpen from '@/types/Bullpen'; +import Pageable from "@/types/PagingDecorator"; import api from '@/services/Api'; +import {ApiService} from "@/services/ApiService"; + +export class BullpenSessionService extends ApiService { + public constructor() { + super('bullpen_session'); + } -export class BullpenSessionService { public create(user: User): Bullpen { return { id: null, @@ -14,16 +20,6 @@ export class BullpenSessionService { } } - public save(bullpen: Bullpen): Promise { - return api - .post('/bullpen_session', bullpen) - .then(response => { - return response.data; - }, (error) => { - console.log(JSON.stringify(error, null, 2)); - }); - } - public createPitch(): Pitch { return { id: undefined, @@ -33,6 +29,20 @@ export class BullpenSessionService { hitArea: 0 } } + + public findByPitcherId(id: number, page: number, size: number): Promise> { + return api + .get('/bullpen_session', { params: { + user: id, + page: page, + size: size + }}).then(response => { + return response.data; + }, (error) => { + this.handleExpiredToken(error); + return Promise.reject(error); + }); + } } export default new BullpenSessionService(); diff --git a/app/src/services/PitchTypeService.ts b/app/src/services/PitchTypeService.ts index fef0f1c..51f7102 100644 --- a/app/src/services/PitchTypeService.ts +++ b/app/src/services/PitchTypeService.ts @@ -1,14 +1,18 @@ import PitchType from "@/types/PitchType"; +import { ApiService } from './ApiService'; -class PitchTypeService { - getLocalPitchTypes(): PitchType[] { +class PitchTypeService extends ApiService { + constructor() { + super('pitch_types'); + } + + public getLocalPitchTypes(): PitchType[] { return JSON.parse(localStorage.getItem("pitchTypes") || '""'); } - updateLocalPitchTypes(pitchTypes: PitchType[]): void { + public updateLocalPitchTypes(pitchTypes: PitchType[]): void { localStorage.setItem("pitchTypes", JSON.stringify(pitchTypes)); } - } export default new PitchTypeService(); diff --git a/app/src/services/UserService.ts b/app/src/services/UserService.ts index e60ffe7..d643584 100644 --- a/app/src/services/UserService.ts +++ b/app/src/services/UserService.ts @@ -1,13 +1,9 @@ import User from "@/types/User"; -import api from './Api'; +import { ApiService } from './ApiService'; -class UserService { - async getAllUsers(): Promise { - return (await api.get('/users')).data; - } - - async getUser(id: number): Promise { - return (await api.get(`/users/${id}`)).data; +class UserService extends ApiService { + constructor() { + super('users'); } } diff --git a/app/src/store/auth.ts b/app/src/store/auth.ts index 6ebe186..d3ade7d 100644 --- a/app/src/store/auth.ts +++ b/app/src/store/auth.ts @@ -27,7 +27,7 @@ const auth: Module = { login({ commit }: AuthActionContext, user) { return AuthService.login(user.email, user.password).then( auth => { - return UserService.getUser(auth.id).then( + return UserService.fetchById(auth.id).then( (user: User) => { commit('loginSuccess', user); return Promise.resolve(user); @@ -62,6 +62,9 @@ const auth: Module = { return Promise.reject(error); } ); + }, + refreshToken({ commit }: AuthActionContext, accessToken) { + commit('refreshToken', accessToken); } }, mutations: { @@ -83,6 +86,10 @@ const auth: Module = { }, registerFailure(state) { state.isAuthenticated = false; + }, + refreshToken(state, accessToken: string) { + state.isAuthenticated = true; + TokenService.updateLocalAccessToken(accessToken); } }, getters: { diff --git a/app/src/store/bullpen.ts b/app/src/store/bullpen.ts index adf8416..7de9cb7 100644 --- a/app/src/store/bullpen.ts +++ b/app/src/store/bullpen.ts @@ -4,25 +4,26 @@ import Bullpen from '@/types/Bullpen'; import User from '@/types/User'; import BullpenSessionService from '@/services/BullpenSessionService'; import Pitch from '@/types/Pitch'; +import Pageable from "@/types/PagingDecorator"; export interface BullpenState { bullpen: Bullpen | null; + bullpens: Pageable | null; } type BullpenActionContext = ActionContext; const bullpen: Module = { namespaced: true, - state: { bullpen: null }, + state: { + bullpen: null, + bullpens: null + }, actions: { start({commit}: BullpenActionContext, user: User) { const bullpen = BullpenSessionService.create(user); commit('start', bullpen); }, - async finish({commit}: BullpenActionContext, bullpen: Bullpen) { - await BullpenSessionService.save(bullpen); - commit('finish'); - } }, mutations: { start(state, bullpen: Bullpen) { @@ -34,6 +35,9 @@ const bullpen: Module = { }, finish(state) { state.bullpen = null; + }, + fetch(state: BullpenState, bullpens: Pageable) { + state.bullpens = bullpens; } } }; diff --git a/app/src/store/pitchType.ts b/app/src/store/pitchType.ts index efc9938..be9913c 100644 --- a/app/src/store/pitchType.ts +++ b/app/src/store/pitchType.ts @@ -1,36 +1,18 @@ import pitchTypeService from '@/services/PitchTypeService' import PitchType from "@/types/PitchType"; -import api from "@/services/Api"; -import authHeader from '@/services/AuthHeader'; -import { Module, ActionContext } from 'vuex'; +import { Module } from 'vuex'; import { RootState } from './index'; -import {AxiosResponse} from 'axios'; export interface PitchTypeState { pitchTypes: PitchType[]; } -type PitchTypeActionContext = ActionContext; - const pitchTypes: Module = { namespaced: true, state: {pitchTypes: []}, - actions: { - initialize({ commit }: PitchTypeActionContext) { - api.get('/pitch_types', { headers: authHeader() }).then( - (response: AxiosResponse) => { - commit('initializedPitchTypes', response.data); - return Promise.resolve(response.data); - }, - error => { - return Promise.reject(error); - }); - - } - }, mutations: { - initializedPitchTypes(state, pitchTypeList: PitchType[]) { + initialize(state, pitchTypeList: PitchType[]) { pitchTypeService.updateLocalPitchTypes(pitchTypeList); state.pitchTypes = pitchTypeList; } diff --git a/app/src/types/PagingDecorator.ts b/app/src/types/PagingDecorator.ts new file mode 100644 index 0000000..be85d79 --- /dev/null +++ b/app/src/types/PagingDecorator.ts @@ -0,0 +1,6 @@ +export default interface Pageable { + totalCount: number, + totalPages: number, + currentPage: number, + data: Type[] +} \ No newline at end of file diff --git a/app/src/views/BullpenListView.vue b/app/src/views/BullpenListView.vue new file mode 100644 index 0000000..8c0fcd0 --- /dev/null +++ b/app/src/views/BullpenListView.vue @@ -0,0 +1,51 @@ + + + + + \ No newline at end of file diff --git a/app/src/views/BullpenSummaryView.vue b/app/src/views/BullpenSummaryView.vue index 58fca7b..eb84926 100644 --- a/app/src/views/BullpenSummaryView.vue +++ b/app/src/views/BullpenSummaryView.vue @@ -20,6 +20,7 @@ import PitchType from "@/types/PitchType"; import {useRouter} from 'vue-router'; import {computed} from 'vue'; import {useStore} from 'vuex'; +import BullpenSessionService from "@/services/BullpenSessionService"; const router = useRouter(); const store = useStore(); @@ -36,8 +37,13 @@ const determinePitchTypeName = (id: number): string => { } const gotoHome = () => { - store.dispatch("bullpen/finish", bullpen.value.bullpen); - router.push({path: '/home'}); + BullpenSessionService.save(bullpen.value.bullpen).then((bullpen) => { + store.commit("bullpen/finish", bullpen); + router.push({path: '/home'}); + }, (error) => { + // Todo: Handle error + console.log(error); + }); } diff --git a/app/src/views/BullpenView.vue b/app/src/views/BullpenView.vue index 6cff5a9..8b8ef42 100644 --- a/app/src/views/BullpenView.vue +++ b/app/src/views/BullpenView.vue @@ -1,5 +1,5 @@