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
This commit is contained in:
2025-10-12 06:11:54 +05:00
parent fd9be2199c
commit 237ee6742e
8 changed files with 229 additions and 201 deletions
+2 -32
View File
@@ -1,44 +1,14 @@
import './assets/main.css' import './assets/main.css'
import { createApp } from 'vue' import { createApp } from 'vue'
import { createPinia } from 'pinia' import pinia from './stores'
import App from './App.vue' import App from './App.vue'
import router from './router' 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) const app = createApp(App)
app.use(createPinia()) app.use(pinia)
app.use(router) app.use(router)
// Инициализация auth store после создания app // Инициализация auth store после создания app
+57 -96
View File
@@ -1,57 +1,45 @@
// stores/auth.js
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import axios from 'axios' import { apiClient, withLoading } from './helpers/api'
const AUTH_API_URL = 'https://begushiybashkir.ru/api/v1/auth'
export const useAuthStore = defineStore('auth', () => { export const useAuthStore = defineStore('auth', () => {
// State
const user = ref(null) const user = ref(null)
const token = ref(localStorage.getItem('auth_token') || '') const token = ref(localStorage.getItem('auth_token') || '')
const loading = ref(false) const loading = ref(false)
const error = ref('') const error = ref('')
const initialized = ref(false)
// Computed свойства // Getters
const isAuthenticated = computed(() => !!token.value && !!user.value) const isAuthenticated = computed(() => !!token.value && !!user.value)
const userFullName = computed(() => { const userFullName = computed(() =>
if (!user.value) return '' user.value ? `${user.value.firstName} ${user.value.lastName}` : ''
return `${user.value.firstName} ${user.value.lastName}` )
})
// Установка токена // Actions
const setToken = (newToken) => { const setToken = (newToken) => {
token.value = newToken token.value = newToken
localStorage.setItem('auth_token', newToken) localStorage.setItem('auth_token', newToken)
// Устанавливаем токен в заголовки axios по умолчанию
axios.defaults.headers.common['Authorization'] = `Bearer ${newToken}`
} }
// Очистка токена const clearAuth = () => {
const clearToken = () => {
token.value = '' token.value = ''
user.value = null
localStorage.removeItem('auth_token') localStorage.removeItem('auth_token')
delete axios.defaults.headers.common['Authorization']
} }
// Установка пользователя
const setUser = (userData) => { const setUser = (userData) => {
user.value = userData user.value = userData
} }
// Очистка пользователя
const clearUser = () => {
user.value = null
}
// Регистрация
const register = async (userData) => { const register = async (userData) => {
loading.value = true // Передаем store объект с loading и error
error.value = '' return withLoading({ loading, error }, async () => {
await apiClient.post('/auth/register', userData)
try { // Auto-login after registration
const response = await axios.post(`${AUTH_API_URL}/register`, userData) const loginResponse = await apiClient.post('/auth/login', {
// После успешной регистрации автоматически логинимся
const loginResponse = await axios.post(`${AUTH_API_URL}/login`, {
email: userData.email, email: userData.email,
password: userData.password password: userData.password
}) })
@@ -60,89 +48,62 @@ export const useAuthStore = defineStore('auth', () => {
setToken(authToken) setToken(authToken)
setUser(userInfo) setUser(userInfo)
return { success: true, data: response.data } return { success: true }
} catch (err) { })
error.value = err.response?.data?.message || err.message || 'Ошибка регистрации'
return { success: false, error: error.value }
} finally {
loading.value = false
}
} }
// Логин
const login = async (credentials) => { const login = async (credentials) => {
loading.value = true return withLoading({ loading, error }, async () => {
error.value = '' const response = await apiClient.post('/auth/login', credentials)
try {
const response = await axios.post(`${AUTH_API_URL}/login`, credentials)
const { token: authToken, user: userInfo } = response.data const { token: authToken, user: userInfo } = response.data
console.log("authToken: " + authToken + "userInfo: " + userInfo)
setToken(authToken) setToken(authToken)
setUser(userInfo) setUser(userInfo)
return { success: true, data: response.data } 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
}
} }
// Получение профиля const logout = async () => {
const fetchProfile = async () => { return withLoading({ loading, error }, async () => {
loading.value = true
error.value = ''
try { try {
const response = await axios.get(`${AUTH_API_URL}/profile`) await apiClient.post('/auth/logout')
} catch (err) {
console.error('Logout error:', err)
} finally {
clearAuth()
}
return { success: true }
})
}
const fetchProfile = async () => {
return withLoading({ loading, error }, async () => {
const response = await apiClient.get('/user/profile')
setUser(response.data) setUser(response.data)
return { success: true, data: 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) => {
const initializeAuth = async () => { return withLoading({ loading, error }, async () => {
if (token.value) { const response = await apiClient.post('/user/editProfile', profileData)
// Восстанавливаем заголовок авторизации setUser(response.data)
axios.defaults.headers.common['Authorization'] = `Bearer ${token.value}` return { success: true, data: response.data }
try { })
// Загружаем данные пользователя
await fetchProfile()
} catch (error) {
console.error('Ошибка инициализации авторизации:', error)
// Если токен невалидный, очищаем его
clearToken()
clearUser()
} }
const initializeAuth = async () => {
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, token,
loading, loading,
error, error,
initialized,
// Getters // Getters
isAuthenticated, isAuthenticated,
@@ -162,9 +124,8 @@ export const useAuthStore = defineStore('auth', () => {
login, login,
logout, logout,
fetchProfile, fetchProfile,
updateProfile,
initializeAuth, initializeAuth,
setToken, clearAuth
clearToken,
clearUser
} }
}) })
@@ -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
}
}
}
}
@@ -0,0 +1,6 @@
// stores/index.js
import { createPinia } from 'pinia'
const pinia = createPinia()
export default pinia
@@ -0,0 +1,7 @@
// stores/plugins/persistence.js
export const authPersistPlugin = ({ store }) => {
// Восстанавливаем состояние при инициализации
if (store.$id === 'auth') {
store.initializeAuth()
}
}
+61 -58
View File
@@ -1,26 +1,53 @@
// stores/user.js
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
// import axios from 'axios' import { handleApiError } from './helpers/api'
// const API_BASE_URL = 'https://begushiybashkir.ru/api/v1'
export const useUserStore = defineStore('user', () => { export const useUserStore = defineStore('user', () => {
// State
const userStats = ref(null) const userStats = ref(null)
const userTraining = ref(null) const userTraining = ref(null)
const userAchievements = ref([]) const userAchievements = ref([])
const loading = ref(false) const loading = ref(false)
const error = ref('') const error = ref('')
// Получение статистики пользователя // Getters
const fetchUserStats = async () => { 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 loading.value = true
error.value = '' error.value = ''
try { 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
await new Promise(resolve => setTimeout(resolve, 1000)) const fetchUserStats = async () => {
return withStoreLoading(async () => {
// TODO: Заменить на реальный endpoint когда будет готов
// const response = await apiClient.get('/user/stats')
// Временные мок данные
await new Promise(resolve => setTimeout(resolve, 500))
userStats.value = { userStats.value = {
totalDistance: 245, totalDistance: 245,
@@ -33,24 +60,15 @@ export const useUserStore = defineStore('user', () => {
} }
return { success: true, data: userStats.value } 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 () => { const fetchUserTraining = async () => {
loading.value = true return withStoreLoading(async () => {
error.value = '' // TODO: Заменить на реальный endpoint когда будет готов
// const response = await apiClient.get('/user/training')
try { await new Promise(resolve => setTimeout(resolve, 500))
// TODO: Заменить на реальный endpoint
// const response = await axios.get(`${API_BASE_URL}/user/training`)
await new Promise(resolve => setTimeout(resolve, 1000))
userTraining.value = { userTraining.value = {
currentWeek: 4, currentWeek: 4,
@@ -64,24 +82,15 @@ export const useUserStore = defineStore('user', () => {
} }
return { success: true, data: userTraining.value } 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 () => { const fetchUserAchievements = async () => {
loading.value = true return withStoreLoading(async () => {
error.value = '' // TODO: Заменить на реальный endpoint когда будет готов
// const response = await apiClient.get('/user/achievements')
try { await new Promise(resolve => setTimeout(resolve, 500))
// TODO: Заменить на реальный endpoint
// const response = await axios.get(`${API_BASE_URL}/user/achievements`)
await new Promise(resolve => setTimeout(resolve, 1000))
userAchievements.value = [ userAchievements.value = [
{ id: 1, name: 'Первый забег', description: 'Пробежать первую 5км', achieved: true, date: '2024-01-20' }, { 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 } 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 { return {
// State // State
@@ -130,6 +132,7 @@ export const useUserStore = defineStore('user', () => {
// Actions // Actions
fetchUserStats, fetchUserStats,
fetchUserTraining, fetchUserTraining,
fetchUserAchievements fetchUserAchievements,
fetchAllUserData
} }
}) })
@@ -6,7 +6,6 @@ import (
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
"gorm.io/gorm" "gorm.io/gorm"
) )
type User struct { type User struct {
@@ -0,0 +1,5 @@
package models
type Workout struct {
ID uint
}