From 237ee6742eef838cbd7493251957bd6334feba24 Mon Sep 17 00:00:00 2001 From: valitovgaziz Date: Sun, 12 Oct 2025 06:11:54 +0500 Subject: [PATCH] modified: begushiybashkir/bbvue/src/main.js modified: begushiybashkir/bbvue/src/stores/auth.js new file: begushiybashkir/bbvue/src/stores/helpers/api.js new file: begushiybashkir/bbvue/src/stores/index.js new file: begushiybashkir/bbvue/src/stores/plugins/persistence.js modified: begushiybashkir/bbvue/src/stores/user.js modified: serv_nginx/api_bb/internal/models/user.go new file: serv_nginx/api_bb/internal/models/workout.go fix save store, add helpers, add new models --- begushiybashkir/bbvue/src/main.js | 34 +--- begushiybashkir/bbvue/src/stores/auth.js | 151 +++++++----------- .../bbvue/src/stores/helpers/api.js | 77 +++++++++ begushiybashkir/bbvue/src/stores/index.js | 6 + .../bbvue/src/stores/plugins/persistence.js | 7 + begushiybashkir/bbvue/src/stores/user.js | 117 +++++++------- serv_nginx/api_bb/internal/models/user.go | 33 ++-- serv_nginx/api_bb/internal/models/workout.go | 5 + 8 files changed, 229 insertions(+), 201 deletions(-) create mode 100644 begushiybashkir/bbvue/src/stores/helpers/api.js create mode 100644 begushiybashkir/bbvue/src/stores/index.js create mode 100644 begushiybashkir/bbvue/src/stores/plugins/persistence.js create mode 100644 serv_nginx/api_bb/internal/models/workout.go diff --git a/begushiybashkir/bbvue/src/main.js b/begushiybashkir/bbvue/src/main.js index 76181bf..351ad15 100644 --- a/begushiybashkir/bbvue/src/main.js +++ b/begushiybashkir/bbvue/src/main.js @@ -1,44 +1,14 @@ import './assets/main.css' import { createApp } from 'vue' -import { createPinia } from 'pinia' +import pinia from './stores' import App from './App.vue' import router from './router' -import axios from 'axios' - -// Глобальная конфигурация axios -axios.defaults.baseURL = 'https://begushiybashkir.ru/api/v1' -axios.defaults.withCredentials = true // Для работы с куками - -// Интерцептор для автоматического добавления токена -axios.interceptors.request.use((config) => { - const token = localStorage.getItem('auth_token') - if (token) { - config.headers.Authorization = `Bearer ${token}` - } - return config -}) - -// Интерцептор для обработки ошибок авторизации -axios.interceptors.response.use( - (response) => response, - (error) => { - if (error.response?.status === 401) { - // Токен истек или невалиден - const authStore = useAuthStore() - authStore.clearToken() - authStore.clearUser() - window.location.href = '/login' - } - return Promise.reject(error) - } -) - const app = createApp(App) -app.use(createPinia()) +app.use(pinia) app.use(router) // Инициализация auth store после создания app diff --git a/begushiybashkir/bbvue/src/stores/auth.js b/begushiybashkir/bbvue/src/stores/auth.js index de05a2b..63ace0d 100644 --- a/begushiybashkir/bbvue/src/stores/auth.js +++ b/begushiybashkir/bbvue/src/stores/auth.js @@ -1,57 +1,45 @@ +// stores/auth.js import { defineStore } from 'pinia' import { ref, computed } from 'vue' -import axios from 'axios' - -const AUTH_API_URL = 'https://begushiybashkir.ru/api/v1/auth' +import { apiClient, withLoading } from './helpers/api' export const useAuthStore = defineStore('auth', () => { + // State const user = ref(null) const token = ref(localStorage.getItem('auth_token') || '') const loading = ref(false) const error = ref('') + const initialized = ref(false) - // Computed свойства + // Getters const isAuthenticated = computed(() => !!token.value && !!user.value) - const userFullName = computed(() => { - if (!user.value) return '' - return `${user.value.firstName} ${user.value.lastName}` - }) + const userFullName = computed(() => + user.value ? `${user.value.firstName} ${user.value.lastName}` : '' + ) - // Установка токена + // Actions const setToken = (newToken) => { token.value = newToken localStorage.setItem('auth_token', newToken) - // Устанавливаем токен в заголовки axios по умолчанию - axios.defaults.headers.common['Authorization'] = `Bearer ${newToken}` } - // Очистка токена - const clearToken = () => { + const clearAuth = () => { token.value = '' + user.value = null localStorage.removeItem('auth_token') - delete axios.defaults.headers.common['Authorization'] } - // Установка пользователя const setUser = (userData) => { user.value = userData } - // Очистка пользователя - const clearUser = () => { - user.value = null - } - - // Регистрация const register = async (userData) => { - loading.value = true - error.value = '' - - try { - const response = await axios.post(`${AUTH_API_URL}/register`, userData) - - // После успешной регистрации автоматически логинимся - const loginResponse = await axios.post(`${AUTH_API_URL}/login`, { + // Передаем store объект с loading и error + return withLoading({ loading, error }, async () => { + await apiClient.post('/auth/register', userData) + + // Auto-login after registration + const loginResponse = await apiClient.post('/auth/login', { email: userData.email, password: userData.password }) @@ -60,89 +48,62 @@ export const useAuthStore = defineStore('auth', () => { setToken(authToken) setUser(userInfo) - return { success: true, data: response.data } - } catch (err) { - error.value = err.response?.data?.message || err.message || 'Ошибка регистрации' - return { success: false, error: error.value } - } finally { - loading.value = false - } + return { success: true } + }) } - // Логин const login = async (credentials) => { - loading.value = true - error.value = '' - - try { - const response = await axios.post(`${AUTH_API_URL}/login`, credentials) + return withLoading({ loading, error }, async () => { + const response = await apiClient.post('/auth/login', credentials) const { token: authToken, user: userInfo } = response.data - console.log("authToken: " + authToken + "userInfo: " + userInfo) setToken(authToken) setUser(userInfo) return { success: true, data: response.data } - } catch (err) { - error.value = err.response?.data?.message || 'Ошибка входа' - return { success: false, error: error.value } - } finally { - loading.value = false - } + }) } - // Выход const logout = async () => { - loading.value = true - - try { - await axios.post(`${AUTH_API_URL}/logout`, {}, { - headers: { - 'Authorization': `Bearer ${token.value}` - } - }) - } catch (err) { - console.error('Ошибка при выходе:', err) - } finally { - clearToken() - clearUser() - loading.value = false - } + return withLoading({ loading, error }, async () => { + try { + await apiClient.post('/auth/logout') + } catch (err) { + console.error('Logout error:', err) + } finally { + clearAuth() + } + return { success: true } + }) } - // Получение профиля const fetchProfile = async () => { - loading.value = true - error.value = '' - - try { - const response = await axios.get(`${AUTH_API_URL}/profile`) + return withLoading({ loading, error }, async () => { + const response = await apiClient.get('/user/profile') setUser(response.data) return { success: true, data: response.data } - } catch (err) { - error.value = err.response?.data?.message || err.message || 'Ошибка загрузки профиля' - clearToken() - clearUser() - return { success: false, error: error.value } - } finally { - loading.value = false - } + }) + } + + const updateProfile = async (profileData) => { + return withLoading({ loading, error }, async () => { + const response = await apiClient.post('/user/editProfile', profileData) + setUser(response.data) + return { success: true, data: response.data } + }) } - // Инициализация при загрузке приложения const initializeAuth = async () => { - if (token.value) { - // Восстанавливаем заголовок авторизации - axios.defaults.headers.common['Authorization'] = `Bearer ${token.value}` - try { - // Загружаем данные пользователя - await fetchProfile() - } catch (error) { - console.error('Ошибка инициализации авторизации:', error) - // Если токен невалидный, очищаем его - clearToken() - clearUser() - } + if (initialized.value || !token.value) return + + initialized.value = true + + try { + await fetchProfile() + console.log('Auth restored successfully') + } catch (err) { + console.error('Auth restoration failed:', err) + clearAuth() } } @@ -152,6 +113,7 @@ export const useAuthStore = defineStore('auth', () => { token, loading, error, + initialized, // Getters isAuthenticated, @@ -162,9 +124,8 @@ export const useAuthStore = defineStore('auth', () => { login, logout, fetchProfile, + updateProfile, initializeAuth, - setToken, - clearToken, - clearUser + clearAuth } }) \ No newline at end of file diff --git a/begushiybashkir/bbvue/src/stores/helpers/api.js b/begushiybashkir/bbvue/src/stores/helpers/api.js new file mode 100644 index 0000000..d6ff4f5 --- /dev/null +++ b/begushiybashkir/bbvue/src/stores/helpers/api.js @@ -0,0 +1,77 @@ +// stores/helpers/api.js +import axios from 'axios' + +const API_BASE_URL = 'https://begushiybashkir.ru/api/v1' + +// Создаем экземпляр axios с базовой конфигурацией +export const apiClient = axios.create({ + baseURL: API_BASE_URL, + withCredentials: true +}) + +// Интерцептор для автоматического добавления токена +apiClient.interceptors.request.use((config) => { + const token = localStorage.getItem('auth_token') + if (token) { + config.headers.Authorization = `Bearer ${token}` + } + return config +}) + +// Интерцептор для обработки ошибок +apiClient.interceptors.response.use( + (response) => response, + (error) => { + if (error.response?.status === 401) { + localStorage.removeItem('auth_token') + window.location.href = '/login' + } + return Promise.reject(error) + } +) + +// Утилита для обработки ошибок +export const handleApiError = (error) => { + const message = error.response?.data?.message || error.message || 'Произошла ошибка' + return { success: false, error: message } +} + +// Утилита для выполнения запросов с loading state +export const withLoading = async (store, fn) => { + store.loading = true + store.error = '' + try { + return await fn() + } catch (error) { + const result = handleApiError(error) + store.error = result.error + return result + } finally { + store.loading = false + } +} + +export const createLoadingHandler = (store) => { + return async (fn) => { + if (store && typeof store.loading !== 'undefined') { + store.loading = true + } + if (store && typeof store.error !== 'undefined') { + store.error = '' + } + + try { + return await fn() + } catch (error) { + const result = handleApiError(error) + if (store && typeof store.error !== 'undefined') { + store.error = result.error + } + return result + } finally { + if (store && typeof store.loading !== 'undefined') { + store.loading = false + } + } + } +} \ No newline at end of file diff --git a/begushiybashkir/bbvue/src/stores/index.js b/begushiybashkir/bbvue/src/stores/index.js new file mode 100644 index 0000000..39c5d42 --- /dev/null +++ b/begushiybashkir/bbvue/src/stores/index.js @@ -0,0 +1,6 @@ +// stores/index.js +import { createPinia } from 'pinia' + +const pinia = createPinia() + +export default pinia \ No newline at end of file diff --git a/begushiybashkir/bbvue/src/stores/plugins/persistence.js b/begushiybashkir/bbvue/src/stores/plugins/persistence.js new file mode 100644 index 0000000..cdf88b0 --- /dev/null +++ b/begushiybashkir/bbvue/src/stores/plugins/persistence.js @@ -0,0 +1,7 @@ +// stores/plugins/persistence.js +export const authPersistPlugin = ({ store }) => { + // Восстанавливаем состояние при инициализации + if (store.$id === 'auth') { + store.initializeAuth() + } +} \ No newline at end of file diff --git a/begushiybashkir/bbvue/src/stores/user.js b/begushiybashkir/bbvue/src/stores/user.js index 7124a5a..7853b1a 100644 --- a/begushiybashkir/bbvue/src/stores/user.js +++ b/begushiybashkir/bbvue/src/stores/user.js @@ -1,26 +1,53 @@ +// stores/user.js import { defineStore } from 'pinia' import { ref, computed } from 'vue' -// import axios from 'axios' - -// const API_BASE_URL = 'https://begushiybashkir.ru/api/v1' +import { handleApiError } from './helpers/api' export const useUserStore = defineStore('user', () => { + // State const userStats = ref(null) const userTraining = ref(null) const userAchievements = ref([]) const loading = ref(false) const error = ref('') - // Получение статистики пользователя - const fetchUserStats = async () => { + // Getters + const completedAchievements = computed(() => + userAchievements.value.filter(achievement => achievement.achieved) + ) + + const pendingAchievements = computed(() => + userAchievements.value.filter(achievement => !achievement.achieved) + ) + + const achievementProgress = computed(() => { + if (!userAchievements.value.length) return 0 + return Math.round((completedAchievements.value.length / userAchievements.value.length) * 100) + }) + + // Вспомогательная функция для обработки loading/error + const withStoreLoading = async (fn) => { loading.value = true error.value = '' - try { - // const response = await axios.get(`${API_BASE_URL}/user/stats`) + return await fn() + } catch (err) { + const result = handleApiError(err) + error.value = result.error + return result + } finally { + loading.value = false + } + } + + // Actions + const fetchUserStats = async () => { + return withStoreLoading(async () => { + // TODO: Заменить на реальный endpoint когда будет готов + // const response = await apiClient.get('/user/stats') - // Временные данные для демонстрации - await new Promise(resolve => setTimeout(resolve, 1000)) + // Временные мок данные + await new Promise(resolve => setTimeout(resolve, 500)) userStats.value = { totalDistance: 245, @@ -33,24 +60,15 @@ export const useUserStore = defineStore('user', () => { } return { success: true, data: userStats.value } - } catch (err) { - error.value = err.response?.data?.message || 'Ошибка загрузки статистики' - return { success: false, error: error.value } - } finally { - loading.value = false - } + }) } - // Получение плана тренировок const fetchUserTraining = async () => { - loading.value = true - error.value = '' - - try { - // TODO: Заменить на реальный endpoint - // const response = await axios.get(`${API_BASE_URL}/user/training`) + return withStoreLoading(async () => { + // TODO: Заменить на реальный endpoint когда будет готов + // const response = await apiClient.get('/user/training') - await new Promise(resolve => setTimeout(resolve, 1000)) + await new Promise(resolve => setTimeout(resolve, 500)) userTraining.value = { currentWeek: 4, @@ -64,24 +82,15 @@ export const useUserStore = defineStore('user', () => { } return { success: true, data: userTraining.value } - } catch (err) { - error.value = err.response?.data?.message || 'Ошибка загрузки плана тренировок' - return { success: false, error: error.value } - } finally { - loading.value = false - } + }) } - // Получение достижений const fetchUserAchievements = async () => { - loading.value = true - error.value = '' - - try { - // TODO: Заменить на реальный endpoint - // const response = await axios.get(`${API_BASE_URL}/user/achievements`) + return withStoreLoading(async () => { + // TODO: Заменить на реальный endpoint когда будет готов + // const response = await apiClient.get('/user/achievements') - await new Promise(resolve => setTimeout(resolve, 1000)) + await new Promise(resolve => setTimeout(resolve, 500)) userAchievements.value = [ { id: 1, name: 'Первый забег', description: 'Пробежать первую 5км', achieved: true, date: '2024-01-20' }, @@ -92,27 +101,20 @@ export const useUserStore = defineStore('user', () => { ] return { success: true, data: userAchievements.value } - } catch (err) { - error.value = err.response?.data?.message || 'Ошибка загрузки достижений' - return { success: false, error: error.value } - } finally { - loading.value = false - } + }) } - // Computed свойства - const completedAchievements = computed(() => - userAchievements.value.filter(achievement => achievement.achieved) - ) - - const pendingAchievements = computed(() => - userAchievements.value.filter(achievement => !achievement.achieved) - ) - - const achievementProgress = computed(() => { - if (!userAchievements.value.length) return 0 - return Math.round((completedAchievements.value.length / userAchievements.value.length) * 100) - }) + // Пакетная загрузка всех данных пользователя + const fetchAllUserData = async () => { + return withStoreLoading(async () => { + await Promise.all([ + fetchUserStats(), + fetchUserTraining(), + fetchUserAchievements() + ]) + return { success: true } + }) + } return { // State @@ -130,6 +132,7 @@ export const useUserStore = defineStore('user', () => { // Actions fetchUserStats, fetchUserTraining, - fetchUserAchievements + fetchUserAchievements, + fetchAllUserData } }) \ No newline at end of file diff --git a/serv_nginx/api_bb/internal/models/user.go b/serv_nginx/api_bb/internal/models/user.go index 57cdfe8..f0ebc01 100644 --- a/serv_nginx/api_bb/internal/models/user.go +++ b/serv_nginx/api_bb/internal/models/user.go @@ -6,23 +6,22 @@ import ( "golang.org/x/crypto/bcrypt" "gorm.io/gorm" - ) type User struct { - ID uint `json:"id" gorm:"primaryKey"` - Email string `json:"email" gorm:"uniqueIndex;not null"` - Password string `json:"-" gorm:"not null"` - FirstName string `json:"first_name" gorm:"not null"` - LastName string `json:"last_name" gorm:"not null"` - Phone string `json:"phone"` - Experience string `json:"experience"` - Goals string `json:"goals"` - Newsletter bool `json:"newsletter"` - Role string `json:"role" gorm:"default:user"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - DeletedAt gorm.DeletedAt `json:"-" gorm:"index"` + ID uint `json:"id" gorm:"primaryKey"` + Email string `json:"email" gorm:"uniqueIndex;not null"` + Password string `json:"-" gorm:"not null"` + FirstName string `json:"first_name" gorm:"not null"` + LastName string `json:"last_name" gorm:"not null"` + Phone string `json:"phone"` + Experience string `json:"experience"` + Goals string `json:"goals"` + Newsletter bool `json:"newsletter"` + Role string `json:"role" gorm:"default:user"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DeletedAt gorm.DeletedAt `json:"-" gorm:"index"` } // HashPassword хеширует пароль перед сохранением @@ -37,8 +36,8 @@ func (u *User) HashPassword() error { // CheckPassword проверяет пароль func (u *User) CheckPassword(password string) bool { - err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password)) - return err == nil + err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password)) + return err == nil } // BeforeCreate hook для GORM @@ -56,4 +55,4 @@ func (u *User) BeforeCreate(tx *gorm.DB) error { func (u *User) BeforeUpdate(tx *gorm.DB) error { u.UpdatedAt = time.Now() return nil -} \ No newline at end of file +} diff --git a/serv_nginx/api_bb/internal/models/workout.go b/serv_nginx/api_bb/internal/models/workout.go new file mode 100644 index 0000000..023cfac --- /dev/null +++ b/serv_nginx/api_bb/internal/models/workout.go @@ -0,0 +1,5 @@ +package models + +type Workout struct { + ID uint +} \ No newline at end of file