From 0731123fbb7eecea44640594731b3e8749d38d56 Mon Sep 17 00:00:00 2001 From: Fedorov Vladimir Date: Sat, 18 Jul 2026 16:16:10 +0700 Subject: [PATCH] add storage --- .../crabs/evening_detective_server/client.ts | 60 +++++ src/components/LoginPage.vue | 55 ++++- src/components/OfficePage.vue | 29 +++ src/router/index.ts | 6 + src/stores/auth.ts | 226 ++++++++++++++++++ src/stores/counter.ts | 12 - src/views/OfficeView.vue | 10 + 7 files changed, 375 insertions(+), 23 deletions(-) create mode 100644 src/api/generated/crabs/evening_detective_server/client.ts create mode 100644 src/components/OfficePage.vue create mode 100644 src/stores/auth.ts delete mode 100644 src/stores/counter.ts create mode 100644 src/views/OfficeView.vue diff --git a/src/api/generated/crabs/evening_detective_server/client.ts b/src/api/generated/crabs/evening_detective_server/client.ts new file mode 100644 index 0000000..6fd523c --- /dev/null +++ b/src/api/generated/crabs/evening_detective_server/client.ts @@ -0,0 +1,60 @@ +import { createEveningDetectiveServerClient, type EveningDetectiveServer } from "."; + +type RequestType = { + path: string; + method: string; + body: string | null; +}; + +function createHttpHandler( + baseURL: string, + getToken?: () => string | null, +): ( + request: { path: string, method: string, body: string | null }, +) => Promise { + return async ( + request: { path: string, method: string, body: string | null }, + ) => { + // Формируем полный URL + const url = `${baseURL}/${request.path}`; + + // Подготавливаем заголовки + const headers: Record = { + 'Content-Type': 'application/json', + }; + + // Добавляем токен, если он есть + const token = getToken?.(); + if (token) { + headers['Authorization'] = `Bearer ${token}`; + } + + // Делаем запрос + const response = await fetch(url, { + method: request.method, + headers, + body: request.body, // body уже JSON-строка из генератора + }); + + // Парсим ответ + const data = await response.json(); + + // Возвращаем в формате, который ожидает генератор + return data; + }; +} + +export function getURL(): string { + return "http://localhost:8090" +} + +export function buildClient( + getToken?: () => string | null, +): EveningDetectiveServer { + return createEveningDetectiveServerClient( + createHttpHandler( + getURL(), + getToken, + ) + ) +} diff --git a/src/components/LoginPage.vue b/src/components/LoginPage.vue index 6a4b8a2..2546f81 100644 --- a/src/components/LoginPage.vue +++ b/src/components/LoginPage.vue @@ -12,7 +12,14 @@
- Вход + + + Вход + + + Подождите... + +
@@ -36,8 +43,14 @@
- Регистрация + + + Регистрация + + + Подождите... + +
@@ -50,7 +63,15 @@ diff --git a/src/components/OfficePage.vue b/src/components/OfficePage.vue new file mode 100644 index 0000000..aa161ac --- /dev/null +++ b/src/components/OfficePage.vue @@ -0,0 +1,29 @@ + + + + + diff --git a/src/router/index.ts b/src/router/index.ts index ddf11f9..448412a 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -1,6 +1,7 @@ import { createRouter, createWebHistory } from 'vue-router' import HomeView from '../views/HomeView.vue' import LoginView from '../views/LoginView.vue' +import OfficeView from '../views/OfficeView.vue' const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), @@ -15,6 +16,11 @@ const router = createRouter({ name: 'login', component: LoginView, }, + { + path: '/office', + name: 'office', + component: OfficeView, + }, ], }) diff --git a/src/stores/auth.ts b/src/stores/auth.ts new file mode 100644 index 0000000..226f61a --- /dev/null +++ b/src/stores/auth.ts @@ -0,0 +1,226 @@ +// stores/auth.ts +import { type User } from '@/api/generated/crabs/evening_detective_server'; +import { buildClient, getURL } from '@/api/generated/crabs/evening_detective_server/client'; +import { defineStore } from 'pinia'; +import { ref, computed } from 'vue'; + +export const useAuthStore = defineStore('auth', () => { + // ===== STATE ===== + const accessToken = ref(null); + const refreshToken = ref(null); + const user = ref(null); + const isLoading = ref(false); + const error = ref(null); + + // ===== GETTERS ===== + const isAuthenticated = computed(() => !!accessToken.value); + const username = computed(() => user.value?.username || ''); + const userRoles = computed(() => user.value?.roles || []); + + // ===== ACTIONS ===== + + // Инициализация из localStorage при загрузке + function initFromStorage() { + const token = localStorage.getItem('accessToken'); + const refresh = localStorage.getItem('refreshToken'); + + if (token) { + accessToken.value = token; + refreshToken.value = refresh; + + // Получаем данные пользователя + fetchUser(); + } + } + + function getToken(): string | null { + return accessToken.value + } + + // Получение данных пользователя + async function fetchUser() { + if (!accessToken.value) return; + + try { + const client = buildClient(getToken); + const response = await client.GetMe({}); + + if (response.error) { + error.value = response.error; + return; + } + + if (response.user) { + user.value = response.user; + } + } catch (err) { + console.error('Failed to fetch user:', err); + // Если 401 — токен протух + if (err instanceof Error && err.message.includes('401')) { + await logout(); + } + } + } + + // Логин + async function login(email: string, password: string) { + isLoading.value = true; + error.value = null; + + try { + const client = buildClient(getToken); + const response = await client.Login({ email, password }); + + if (response.error) { + error.value = response.error; + return false; + } + + // Сохраняем токены + if (response.accessToken) { + accessToken.value = response.accessToken; + refreshToken.value = response.refreshToken || null; + + // Сохраняем в localStorage + localStorage.setItem('accessToken', response.accessToken); + if (response.refreshToken) { + localStorage.setItem('refreshToken', response.refreshToken); + } + + // Получаем данные пользователя + await fetchUser(); + + return true; + } + + return false; + } catch (err) { + error.value = err instanceof Error ? err.message : 'Login failed'; + return false; + } finally { + isLoading.value = false; + } + } + + // Обновление токена + async function refreshTokenAction() { + if (!refreshToken.value) return false; + + try { + const client = buildClient(getToken); + const response = await client.Refresh({ + refreshToken: refreshToken.value + }); + + if (response.error || !response.accessToken) { + return false; + } + + // Обновляем токены + accessToken.value = response.accessToken; + refreshToken.value = response.refreshToken || null; + + localStorage.setItem('accessToken', response.accessToken); + if (response.refreshToken) { + localStorage.setItem('refreshToken', response.refreshToken); + } + + return true; + } catch { + return false; + } + } + + // Регистрация + async function signup(username: string, email: string) { + isLoading.value = true; + error.value = null; + + try { + const client = buildClient(getToken); + const response = await client.Signup({ username, email }); + + if (response.error) { + error.value = response.error; + return false; + } + + return true + } catch (err) { + error.value = err instanceof Error ? err.message : 'Signup failed'; + return false; + } finally { + isLoading.value = false; + } + } + + // Восстановление пароля + async function refreshPassword(email: string) { + isLoading.value = true; + error.value = null; + + try { + const client = buildClient(getToken); + const response = await client.RefreshPassword({ email }); + + if (response.error) { + error.value = response.error; + return false; + } + + return true; + } catch (err) { + error.value = err instanceof Error ? err.message : 'Password reset failed'; + return false; + } finally { + isLoading.value = false; + } + } + + // Выход + async function logout() { + accessToken.value = null; + refreshToken.value = null; + user.value = null; + error.value = null; + + localStorage.removeItem('accessToken'); + localStorage.removeItem('refreshToken'); + } + + // Проверка ролей + function hasRole(role: string): boolean { + return userRoles.value.includes(role); + } + + function hasAnyRole(roles: string[]): boolean { + return roles.some(role => userRoles.value.includes(role)); + } + + // ===== RETURN ===== + return { + // State + accessToken, + refreshToken, + user, + isLoading, + error, + + // Getters + isAuthenticated, + username, + userRoles, + + // Actions + initFromStorage, + getToken, + login, + signup, + refreshPassword, + logout, + fetchUser, + refreshTokenAction, + hasRole, + hasAnyRole, + }; +}); \ No newline at end of file diff --git a/src/stores/counter.ts b/src/stores/counter.ts deleted file mode 100644 index b6757ba..0000000 --- a/src/stores/counter.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { ref, computed } from 'vue' -import { defineStore } from 'pinia' - -export const useCounterStore = defineStore('counter', () => { - const count = ref(0) - const doubleCount = computed(() => count.value * 2) - function increment() { - count.value++ - } - - return { count, doubleCount, increment } -}) diff --git a/src/views/OfficeView.vue b/src/views/OfficeView.vue new file mode 100644 index 0000000..8720c42 --- /dev/null +++ b/src/views/OfficeView.vue @@ -0,0 +1,10 @@ + + + +