Skip to content

Commit

Permalink
Merge branch 'development_1.1' into development_1.1_calender
Browse files Browse the repository at this point in the history
  • Loading branch information
maayarosama committed Jan 28, 2024
2 parents 58f3c3d + c02c7c8 commit ec5b131
Show file tree
Hide file tree
Showing 8 changed files with 72 additions and 23 deletions.
9 changes: 7 additions & 2 deletions client/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { useApi } from '@/hooks'
import SideDrawer from './components/SideDrawer.vue'
import { onMounted } from 'vue'
import { useState } from './store'
import { $api } from './clients'
import { useAsyncState } from '@vueuse/core'
export default {
name: 'App',
Expand All @@ -23,8 +25,11 @@ export default {
const state = useState()
api && notifier && api.setNotifier(notifier)
onMounted(() => {
state.access_token.value = localStorage.access_token
onMounted(async() => {
const {access_token, refresh_token, user} = state
access_token.value = localStorage.access_token
refresh_token.value = localStorage.refresh_token
user.value = useAsyncState(async() => await $api.myprofile.getUser(), null).state
})
}
}
Expand Down
2 changes: 1 addition & 1 deletion client/src/clients/api/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export class AuthApi extends ApiClientBase {
this.$http.post<Api.Returns.Refresh>(this.getUrl('/token/refresh/'), input)
)

ApiClientBase.refresh(res)
ApiClientBase.refresh()

return res
}
Expand Down
41 changes: 30 additions & 11 deletions client/src/clients/api/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { NotifierService } from 'vue3-notifier'
import type { ApiClient } from './index'
import { useStorage } from '@vueuse/core'
import { useState } from '@/store'
import { capitalize } from 'vue'

export abstract class ApiClientBase {
public static $api: ApiClient
Expand All @@ -29,29 +30,32 @@ export abstract class ApiClientBase {
protected static login(user: Api.LoginUser) {
ApiClientBase.USER = user
const state = useState()
const { access_token } = state
const { access_token, refresh_token } = state
access_token.value = user.access_token
refresh_token.value = user.refresh_token
useStorage('access_token', access_token.value, localStorage, { mergeDefaults: true })
useStorage('refresh_token', refresh_token.value, localStorage, { mergeDefaults: true })
}

protected static refresh(res: Api.Returns.Refresh) {
protected static refresh() {
const state = useState()
const {access_token, refresh_token } = state;
ApiClientBase.assertUser()
ApiClientBase.USER = {
...ApiClientBase.USER!,
access_token: res.access,
refresh_token: res.refresh
access_token: access_token.value,
refresh_token: refresh_token.value
}
/* TODO: sync in session storage */
}

protected static logout() {
ApiClientBase.assertUser()
ApiClientBase.USER = null
/* TODO: sync in session storage */
}

protected static assertUser() {
if (!ApiClientBase.USER) {
const token = localStorage.getItem("access_token")
if (!ApiClientBase.USER && !token) {
panic(`Expected to login before using this route.`)
}
}
Expand All @@ -63,23 +67,38 @@ export abstract class ApiClientBase {
return this.prePath + this.path + route + '/?' + q
}

private static normalizeError(err: AxiosError<any>) {
return err.response?.data?.detail ?? err.response?.data?.message ?? err.message
private static normalizeError(err: AxiosError<any>): string {
const responseData = err.response?.data;
const errorMessage = responseData?.message ?? err.message;

const errorDetails = Object.entries(responseData?.error || {})
.map(([_, value]) => `${value}`)

Check warning on line 75 in client/src/clients/api/base.ts

View workflow job for this annotation

GitHub Actions / Run linters (18.x)

'_' is defined but never used
.join('. ');

return `${errorMessage}${errorDetails ? `. ${capitalize(errorDetails)}` : ''}`;
}

protected async unwrap<T, R = T>(
req$: Promise<AxiosResponse<T>>,
options: Api.UnwrapOptions<T, R> = {}
) {
const [res, err] = await resolve(req$)
const access_token = localStorage.getItem("access_token")
const refresh_token = localStorage.getItem("refresh_token")

// check if error indicate the token needs to be refreshed
if (
err &&
err.response.status == 401 &&
err.response.data.code == 'token_not_valid' &&
ApiClientBase.USER
ApiClientBase.USER &&
refresh_token !== null
) {
await ApiClientBase.$api.auth.refresh({ refresh: ApiClientBase.USER.refresh_token })
await ApiClientBase.$api.auth.refresh({ refresh: refresh_token })
}
if (err && !ApiClientBase.USER && access_token !== null && refresh_token !== null) {
const user = await ApiClientBase.$api.myprofile.getUser();
ApiClientBase.USER = {...user, access_token, refresh_token}
}

if (err) {
Expand Down
3 changes: 3 additions & 0 deletions client/src/clients/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,12 @@ export class ApiClient extends ApiClientBase {
}

private setAxiosRequestInterceptor() {
const token = localStorage.getItem("access_token")
return this.$http.interceptors.request.use((req) => {
if (ApiClientBase.user) {
req.headers.set('Authorization', 'Bearer ' + ApiClientBase.user.access_token)
} else if (!ApiClientBase.user && token) {
req.headers.set('Authorization', 'Bearer ' + token)
}
return req
})
Expand Down
16 changes: 8 additions & 8 deletions client/src/components/AddOffice.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<v-row class="justify-center align-center">
<v-col cols="12">
<v-text-field
v-model="office.name"
v-model="name"
label="Name"
type="text"
:rules="requiredStringRules"
Expand Down Expand Up @@ -52,22 +52,22 @@ export default {
value: 'Saturday:Sunday'
}
])
const name = ref('')
const selectedWeekend = ref(weekendOptions.value[0])
const countryList = countries.map((c: any) => c.name)
const selectedCountry = ref(countryList[0])
const office = ref({
name: '',
country: selectedCountry.value,
weekend: selectedWeekend.value.value
})
const {execute, isLoading} = useAsyncState(async() => {
await $api.office.create(office.value)
await $api.office.create({
name: name.value,
country: selectedCountry.value,
weekend: selectedWeekend.value.value
})
}, null, {immediate: false})
return {
form,
office,
name,
weekendOptions,
selectedWeekend,
requiredRules,
Expand Down
3 changes: 2 additions & 1 deletion client/src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import { ref } from 'vue'

export const useState = createGlobalState(() => {
const access_token = ref('')
const refresh_token = ref('')
const user = ref()
const rememberMe = ref(false)

return { access_token, user, rememberMe }
return { access_token, refresh_token, user, rememberMe }
})
8 changes: 8 additions & 0 deletions client/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,11 @@ export interface Country {
id: number
country: string
}

export interface JWTokenObject {
token_type: string
exp: number
iat: number
jti: string
user_id: number
}
13 changes: 13 additions & 0 deletions client/src/utils/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import moment from 'moment'

import type { Api } from "@/types"
import type { JWTokenObject } from "@/types"

export async function resolve<T>(promise: Promise<T>): Promise<[T, any]> {
try {
Expand Down Expand Up @@ -149,3 +150,15 @@ export function getStatusColor(status: string) {
return 'grey'
}
}
export function decodeAccessToken(token: string): JWTokenObject {
const base64Url = token.split('.')[1];
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
const jsonPayload = decodeURIComponent(atob(base64).split('').map(function(c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
return JSON.parse(jsonPayload);
}

export function isValidToken(token: string): boolean{
return Date.now() >= decodeAccessToken(token).exp * 1000
}

0 comments on commit ec5b131

Please sign in to comment.