diff --git a/gymboard-app/quasar.extensions.json b/gymboard-app/quasar.extensions.json index 9e26dfe..0967ef4 100644 --- a/gymboard-app/quasar.extensions.json +++ b/gymboard-app/quasar.extensions.json @@ -1 +1 @@ -{} \ No newline at end of file +{} diff --git a/gymboard-app/src/api/main/auth.ts b/gymboard-app/src/api/main/auth.ts index a21eb80..7f3e6c3 100644 --- a/gymboard-app/src/api/main/auth.ts +++ b/gymboard-app/src/api/main/auth.ts @@ -1,4 +1,6 @@ import { api } from 'src/api/main/index'; +import { AuthStoreType } from 'stores/auth-store'; +import Timeout = NodeJS.Timeout; export interface User { id: string; @@ -13,16 +15,38 @@ export interface TokenCredentials { } class AuthModule { - public async getToken(credentials: TokenCredentials): Promise { + private static readonly TOKEN_REFRESH_INTERVAL_MS = 30000; + + private tokenRefreshTimer?: Timeout; + + public async login(authStore: AuthStoreType, credentials: TokenCredentials) { + authStore.token = await this.fetchNewToken(credentials); + authStore.user = await this.fetchMyUser(authStore); + + clearTimeout(this.tokenRefreshTimer); + this.tokenRefreshTimer = setTimeout( + () => this.refreshToken(authStore), + AuthModule.TOKEN_REFRESH_INTERVAL_MS + ); + } + + public logout(authStore: AuthStoreType) { + authStore.$reset(); + clearTimeout(this.tokenRefreshTimer); + } + + private async fetchNewToken(credentials: TokenCredentials): Promise { const response = await api.post('/auth/token', credentials); return response.data.token; } - public async getMyUser(token: string): Promise { - const response = await api.get('/auth/me', { - headers: { - 'Authorization': 'Bearer ' + token - } - }); + + private async refreshToken(authStore: AuthStoreType) { + const response = await api.get('/auth/token', authStore.axiosConfig); + authStore.token = response.data.token; + } + + private async fetchMyUser(authStore: AuthStoreType): Promise { + const response = await api.get('/auth/me', authStore.axiosConfig); return response.data; } } diff --git a/gymboard-app/src/api/main/gyms.ts b/gymboard-app/src/api/main/gyms.ts index 6171b30..62c3d6a 100644 --- a/gymboard-app/src/api/main/gyms.ts +++ b/gymboard-app/src/api/main/gyms.ts @@ -1,7 +1,7 @@ import { GeoPoint } from 'src/api/main/models'; -import SubmissionsModule, {ExerciseSubmission} from 'src/api/main/submission'; +import SubmissionsModule, { ExerciseSubmission } from 'src/api/main/submission'; import { api } from 'src/api/main/index'; -import {GymRoutable} from 'src/router/gym-routing'; +import { GymRoutable } from 'src/router/gym-routing'; export interface Gym { countryCode: string; @@ -49,7 +49,9 @@ class GymsModule { }; } - public async getRecentSubmissions(gym: GymRoutable): Promise> { + public async getRecentSubmissions( + gym: GymRoutable + ): Promise> { const response = await api.get( `/gyms/${gym.countryCode}_${gym.cityShortName}_${gym.shortName}/recent-submissions` ); diff --git a/gymboard-app/src/api/main/leaderboards.ts b/gymboard-app/src/api/main/leaderboards.ts index e801d3c..b876bd3 100644 --- a/gymboard-app/src/api/main/leaderboards.ts +++ b/gymboard-app/src/api/main/leaderboards.ts @@ -3,49 +3,51 @@ import { getGymCompoundId, GymRoutable } from 'src/router/gym-routing'; import { api } from 'src/api/main/index'; export enum LeaderboardTimeframe { - DAY = "DAY", - WEEK = "WEEK", - MONTH = "MONTH", - YEAR = "YEAR", - ALL = "ALL" + DAY = 'DAY', + WEEK = 'WEEK', + MONTH = 'MONTH', + YEAR = 'YEAR', + ALL = 'ALL', } export interface LeaderboardParams { - exerciseShortName?: string; - gyms?: Array; - timeframe?: LeaderboardTimeframe; - page?: number; - size?: number; + exerciseShortName?: string; + gyms?: Array; + timeframe?: LeaderboardTimeframe; + page?: number; + size?: number; } interface RequestParams { - exercise?: string; - gyms?: string; - t?: string; - page?: number; - size?: number; + exercise?: string; + gyms?: string; + t?: string; + page?: number; + size?: number; } class LeaderboardsModule { - public async getLeaderboard(params: LeaderboardParams): Promise> { - const requestParams: RequestParams = {}; - if (params.exerciseShortName) { - requestParams.exercise = params.exerciseShortName; - } - if (params.gyms) { - requestParams.gyms = params.gyms - .map(gym => getGymCompoundId(gym)) - .join(','); - } - if (params.timeframe) { - requestParams.t = params.timeframe; - } - if (params.page) requestParams.page = params.page; - if (params.size) requestParams.size = params.size; - - const response = await api.get('/leaderboards', { params: requestParams }); - return response.data.content; + public async getLeaderboard( + params: LeaderboardParams + ): Promise> { + const requestParams: RequestParams = {}; + if (params.exerciseShortName) { + requestParams.exercise = params.exerciseShortName; } + if (params.gyms) { + requestParams.gyms = params.gyms + .map((gym) => getGymCompoundId(gym)) + .join(','); + } + if (params.timeframe) { + requestParams.t = params.timeframe; + } + if (params.page) requestParams.page = params.page; + if (params.size) requestParams.size = params.size; + + const response = await api.get('/leaderboards', { params: requestParams }); + return response.data.content; + } } export default LeaderboardsModule; diff --git a/gymboard-app/src/api/main/submission.ts b/gymboard-app/src/api/main/submission.ts index df6364e..dc2bebc 100644 --- a/gymboard-app/src/api/main/submission.ts +++ b/gymboard-app/src/api/main/submission.ts @@ -1,6 +1,6 @@ import { SimpleGym } from 'src/api/main/gyms'; import { Exercise } from 'src/api/main/exercises'; -import {api, BASE_URL} from 'src/api/main/index'; +import { api, BASE_URL } from 'src/api/main/index'; import { getGymCompoundId, GymRoutable } from 'src/router/gym-routing'; import { sleep } from 'src/utils'; @@ -38,10 +38,10 @@ export enum ExerciseSubmissionStatus { } class SubmissionsModule { - public async getSubmission(submissionId: string): Promise { - const response = await api.get( - `/submissions/${submissionId}` - ); + public async getSubmission( + submissionId: string + ): Promise { + const response = await api.get(`/submissions/${submissionId}`); return response.data; } @@ -52,7 +52,7 @@ class SubmissionsModule { ) { return null; } - return BASE_URL + `/submissions/${submission.id}/video` + return BASE_URL + `/submissions/${submission.id}/video`; } public async createSubmission( @@ -60,10 +60,7 @@ class SubmissionsModule { payload: ExerciseSubmissionPayload ): Promise { const gymId = getGymCompoundId(gym); - const response = await api.post( - `/gyms/${gymId}/submissions`, - payload - ); + const response = await api.post(`/gyms/${gymId}/submissions`, payload); return response.data; } @@ -85,7 +82,9 @@ class SubmissionsModule { * Asynchronous method that waits until a submission is done processing. * @param submissionId The submission's id. */ - public async waitUntilSubmissionProcessed(submissionId: string): Promise { + public async waitUntilSubmissionProcessed( + submissionId: string + ): Promise { let failureCount = 0; let attemptCount = 0; while (failureCount < 5 && attemptCount < 60) { diff --git a/gymboard-app/src/components/AccountMenuItem.vue b/gymboard-app/src/components/AccountMenuItem.vue new file mode 100644 index 0000000..48321bd --- /dev/null +++ b/gymboard-app/src/components/AccountMenuItem.vue @@ -0,0 +1,36 @@ + + + + + diff --git a/gymboard-app/src/components/ExerciseSubmissionListItem.vue b/gymboard-app/src/components/ExerciseSubmissionListItem.vue index bdf8a26..057d092 100644 --- a/gymboard-app/src/components/ExerciseSubmissionListItem.vue +++ b/gymboard-app/src/components/ExerciseSubmissionListItem.vue @@ -1,7 +1,15 @@ diff --git a/gymboard-app/src/components/LocaleSelect.vue b/gymboard-app/src/components/LocaleSelect.vue new file mode 100644 index 0000000..956cd59 --- /dev/null +++ b/gymboard-app/src/components/LocaleSelect.vue @@ -0,0 +1,31 @@ + + + + + diff --git a/gymboard-app/src/i18n/en-US/index.ts b/gymboard-app/src/i18n/en-US/index.ts index fec432c..4c5961f 100644 --- a/gymboard-app/src/i18n/en-US/index.ts +++ b/gymboard-app/src/i18n/en-US/index.ts @@ -12,7 +12,7 @@ export default { leaderboard: 'Leaderboard', homePage: { overview: 'Overview of this gym:', - recentLifts: 'Recent Lifts' + recentLifts: 'Recent Lifts', }, submitPage: { name: 'Your Name', diff --git a/gymboard-app/src/i18n/nl-NL/index.ts b/gymboard-app/src/i18n/nl-NL/index.ts index 97848c8..cfccf3e 100644 --- a/gymboard-app/src/i18n/nl-NL/index.ts +++ b/gymboard-app/src/i18n/nl-NL/index.ts @@ -12,7 +12,7 @@ export default { leaderboard: 'Scorebord', homePage: { overview: 'Overzicht van dit sportschool:', - recentLifts: 'Recente liften' + recentLifts: 'Recente liften', }, submitPage: { name: 'Jouw naam', diff --git a/gymboard-app/src/layouts/MainLayout.vue b/gymboard-app/src/layouts/MainLayout.vue index d842419..d29ab5e 100644 --- a/gymboard-app/src/layouts/MainLayout.vue +++ b/gymboard-app/src/layouts/MainLayout.vue @@ -16,23 +16,8 @@ >Gymboard - + + @@ -55,13 +40,8 @@ - + diff --git a/gymboard-app/src/pages/gym/GymHomePage.vue b/gymboard-app/src/pages/gym/GymHomePage.vue index fe2896a..d681a1f 100644 --- a/gymboard-app/src/pages/gym/GymHomePage.vue +++ b/gymboard-app/src/pages/gym/GymHomePage.vue @@ -4,11 +4,22 @@

{{ $t('gymPage.homePage.overview') }}

    -
  • Website: {{ gym.websiteUrl }}
  • -
  • Address: {{ gym.streetAddress }}
  • -
  • City: {{ gym.cityName }}
  • -
  • Country: {{ gym.countryName }}
  • -
  • Registered at: {{ gym.createdAt }}
  • +
  • + Website: + {{ gym.websiteUrl }} +
  • +
  • + Address: {{ gym.streetAddress }} +
  • +
  • + City: {{ gym.cityName }} +
  • +
  • + Country: {{ gym.countryName }} +
  • +
  • + Registered at: {{ gym.createdAt }} +
@@ -19,27 +30,32 @@

{{ $t('gymPage.homePage.recentLifts') }}

- +
diff --git a/gymboard-app/src/router/gym-routing.ts b/gymboard-app/src/router/gym-routing.ts index cad3c97..0f36475 100644 --- a/gymboard-app/src/router/gym-routing.ts +++ b/gymboard-app/src/router/gym-routing.ts @@ -38,4 +38,4 @@ export async function getGymFromRoute(): Promise { */ export function getGymCompoundId(gym: GymRoutable): string { return `${gym.countryCode}_${gym.cityShortName}_${gym.shortName}`; -} \ No newline at end of file +} diff --git a/gymboard-app/src/stores/auth-store.ts b/gymboard-app/src/stores/auth-store.ts new file mode 100644 index 0000000..3337b8e --- /dev/null +++ b/gymboard-app/src/stores/auth-store.ts @@ -0,0 +1,34 @@ +/** + * This store keeps track of the authentication state of the web app, which + * is just keeping the current user and their token. + * + * See src/api/main/auth.ts for mutators of this store. + */ + +import { defineStore } from 'pinia'; +import { User } from 'src/api/main/auth'; + +interface AuthState { + user: User | null; + token: string | null; +} + +export const useAuthStore = defineStore('authStore', { + state: (): AuthState => { + return { user: null, token: null }; + }, + getters: { + loggedIn: (state) => state.user !== null && state.token !== null, + axiosConfig(state) { + if (this.token !== null) { + return { + headers: { Authorization: 'Bearer ' + state.token }, + }; + } else { + return {}; + } + }, + }, +}); + +export type AuthStoreType = ReturnType;