diff --git a/src/App.vue b/src/App.vue
index da9d61a..51d8927 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -2,10 +2,15 @@
- Home
- Login
+
+ Home
+ Login
+
+ Router view starts below
+
diff --git a/src/main.js b/src/main.js
index f71bc34..fda1e6e 100644
--- a/src/main.js
+++ b/src/main.js
@@ -3,12 +3,10 @@ import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
-import { setupAuthInterceptor } from '@/services/setupInterceptors.js'
const app = createApp(App)
app.use(createPinia())
app.use(router)
-setupAuthInterceptor()
app.mount('#app')
diff --git a/src/router/index.js b/src/router/index.js
index 3e2ff9d..b6eb2a6 100644
--- a/src/router/index.js
+++ b/src/router/index.js
@@ -1,5 +1,5 @@
import { createRouter, createWebHistory } from 'vue-router'
-import HomeView from '../views/HomeView.vue'
+import StartPageView from '../views/StartPageView.vue'
import LoginView from '@/views/LoginView.vue'
const router = createRouter({
@@ -7,8 +7,8 @@ const router = createRouter({
routes: [
{
path: '/',
- name: 'home',
- component: HomeView,
+ name: 'startPage',
+ component: StartPageView,
},
{
path: '/login',
diff --git a/src/services/apiClient.js b/src/services/apiClient.js
index 7844a0b..cd53f97 100644
--- a/src/services/apiClient.js
+++ b/src/services/apiClient.js
@@ -1,11 +1,113 @@
-import axios from 'axios'
+import { useAuthStore } from '@/stores/authStore.js';
-const apiClient = axios.create({
- baseURL: 'http://localhost:9000/api/v2',
- timeout: 5000,
- headers: {
+const BASE_URL = 'http://localhost:9000/api/v2';
+
+let isRefreshing = false;
+let refreshPromise = null;
+
+function createConfig(options = {}) {
+ const authStore = useAuthStore();
+ const headers = {
'Content-Type': 'application/json',
- }
-})
+ ...options.headers,
+ };
-export default apiClient
+ if (authStore.accessToken) {
+ headers['Authorization'] = `Bearer ${authStore.accessToken}`;
+ }
+
+ return {
+ headers,
+ credentials: 'include',
+ ...options,
+ };
+}
+
+async function refreshAccessToken() {
+ const authStore = useAuthStore();
+
+ if (isRefreshing) return refreshPromise;
+
+ isRefreshing = true;
+ refreshPromise = fetch(`${BASE_URL}/user/auth/refresh`, {
+ method: 'POST',
+ credentials: 'include',
+ })
+ .then(async (res) => {
+ if (!res.ok) throw new Error('Failed to refresh access token');
+ return res.json();
+ })
+ .then((data) => {
+ authStore.setToken(data.accessToken);
+ return data;
+ })
+ .catch((error) => {
+ throw error;
+ })
+ .finally(() => {
+ isRefreshing = false;
+ refreshPromise = null;
+ });
+
+ return refreshPromise;
+}
+
+async function request(url, options = {}, isRetry = false) {
+ const config = createConfig(options);
+
+ const response = await fetch(`${BASE_URL}${url}`, config);
+
+ if (response.status === 401 && !isRetry) {
+ try {
+ const data = await refreshAccessToken();
+
+ const newOptions = {
+ ...options,
+ headers: {
+ ...options.headers,
+ 'Authorization': `Bearer ${data.accessToken}`,
+ },
+ };
+ return await request(url, newOptions, true);
+ } catch (e) {
+ const authStore = useAuthStore();
+ authStore.forceLogout();
+ throw e;
+ }
+ }
+
+ if (!response.ok) {
+ let errorData;
+ try {
+ errorData = await response.json();
+ } catch {
+ errorData = {};
+ }
+ throw new Error(errorData.message || `HTTP Error: ${response.status}`);
+ }
+
+ const contentType = response.headers.get('content-type');
+ if (contentType?.includes('application/json')) {
+ try {
+ return await response.json();
+ } catch (e) {
+ console.warn('Failed to parse JSON response', e);
+ return null;
+ }
+ }
+
+ return null;
+}
+
+export const apiClient = {
+ get: (url) => request(url, { method: 'GET' }),
+ post: (url, data) => request(url, {
+ method: 'POST',
+ body: JSON.stringify(data),
+ }),
+ put: (url, data) => request(url, {
+ method: 'PUT',
+ body: JSON.stringify(data),
+ }),
+ delete: (url) => request(url, { method: 'DELETE' }),
+};
diff --git a/src/services/setupInterceptors.js b/src/services/setupInterceptors.js
deleted file mode 100644
index 48024e5..0000000
--- a/src/services/setupInterceptors.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import apiClient from '@/services/apiClient'
-import { useAuthStore } from '@/stores/authStore'
-
-export function setupAuthInterceptor() {
- apiClient.interceptors.request.use((config) => {
- const authStore = useAuthStore()
- if (authStore.accessToken) {
- config.headers.Authorization = `Bearer ${authStore.accessToken}`
- }
- return config
- })
-}
diff --git a/src/stores/authStore.js b/src/stores/authStore.js
index 180cd10..2ce7d63 100644
--- a/src/stores/authStore.js
+++ b/src/stores/authStore.js
@@ -1,41 +1,59 @@
import { defineStore } from 'pinia';
import { computed, ref } from 'vue';
-import apiClient from '@/services/apiClient';
+import { apiClient } from '@/services/apiClient';
+import router from '@/router/index.js';
export const useAuthStore = defineStore('auth', () => {
// state
- const accessToken = ref(null)
- const user = ref(null)
+ const accessToken = ref(null);
+ const user = ref(null);
// getters
- const isAuthenticated = computed(() => !!accessToken.value)
+ const isAuthenticated = computed(() => !!accessToken.value);
// actions
+ const setToken = (token) => {
+ accessToken.value = token;
+ };
+
const login = async (email, password) => {
try {
- const response = await apiClient.post(
- "/user/login",
- { email, password }
- )
- const { access_token } = response.data
- accessToken.value = access_token
+ const response = await apiClient.post('/user/auth/login', { email, password });
+ const { access_token, user: userData } = response;
- console.log('Email', email)
- console.log('Password', password)
+ setToken(access_token);
+ user.value = userData || null;
+
+ router.push('/');
} catch (error) {
- console.log(error)
+ console.error('Login error:', error);
}
- }
+ };
- const logout = () => {
- console.log('logout placeholder')
- }
+ const logout = async () => {
+ accessToken.value = null;
+ user.value = null;
+ try {
+ await apiClient.post('/user/auth/logout');
+ } catch (error) {
+ console.error('Logout error:', error);
+ }
+ router.push('/startPage');
+ };
+
+ const forceLogout = () => {
+ accessToken.value = null;
+ user.value = null;
+ router.push('/startPage');
+ };
return {
accessToken,
user,
isAuthenticated,
+ setToken,
login,
- logout
- }
-})
+ logout,
+ forceLogout,
+ };
+});
diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue
deleted file mode 100644
index 3b786e1..0000000
--- a/src/views/HomeView.vue
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-Home view
-
diff --git a/src/views/LoginView.vue b/src/views/LoginView.vue
index eb858ec..ecb8222 100644
--- a/src/views/LoginView.vue
+++ b/src/views/LoginView.vue
@@ -1,16 +1,16 @@
-
@@ -35,5 +35,7 @@
diff --git a/src/views/StartPageView.vue b/src/views/StartPageView.vue
new file mode 100644
index 0000000..161eb51
--- /dev/null
+++ b/src/views/StartPageView.vue
@@ -0,0 +1,20 @@
+
+
+
+ Home view
+
+
+
+