Описание изменений

This commit is contained in:
2025-03-20 12:06:03 +03:00
parent e0d60ff473
commit a98710c12c
17 changed files with 131 additions and 620 deletions

View File

@@ -20,9 +20,16 @@ const adminRoutes = require('./routes/admin');
const app = express(); const app = express();
// Указываем хост явно
app.set('host', '127.0.0.1');
app.set('port', process.env.PORT || 8000);
// Настройка CORS // Настройка CORS
app.use(cors({ app.use(cors({
origin: 'http://localhost:5173', origin: [
'http://localhost:5173',
'http://127.0.0.1:5173' // Добавляем альтернативный origin
],
credentials: true, credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'Cookie'] allowedHeaders: ['Content-Type', 'Authorization', 'Cookie']

View File

@@ -65,10 +65,11 @@ app.get('/api/health', (req, res) => {
}); });
// Запуск сервера // Запуск сервера
app.listen(PORT, async () => { const host = app.get('host');
app.listen(PORT, host, async () => {
try { try {
await initServices(); await initServices();
console.log('Server is running on port', PORT); console.log(`Server is running on http://${host}:${PORT}`);
} catch (error) { } catch (error) {
console.error('Error starting server:', error); console.error('Error starting server:', error);
process.exit(1); process.exit(1);

View File

@@ -68,6 +68,7 @@ class TelegramBotService {
} catch (error) { } catch (error) {
logger.error(`Error sending welcome message to ${chatId}:`, error); logger.error(`Error sending welcome message to ${chatId}:`, error);
} }
} }
async handleVerificationCode(msg) { async handleVerificationCode(msg) {

View File

@@ -17,7 +17,6 @@
"buffer": "^6.0.3", "buffer": "^6.0.3",
"connect-pg-simple": "^10.0.0", "connect-pg-simple": "^10.0.0",
"ethers": "6.13.5", "ethers": "6.13.5",
"pinia": "^2.0.33",
"siwe": "^2.1.4", "siwe": "^2.1.4",
"sortablejs": "^1.15.6", "sortablejs": "^1.15.6",
"vue": "^3.2.47", "vue": "^3.2.47",

View File

@@ -5,29 +5,60 @@
</template> </template>
<script setup> <script setup>
import { onMounted } from 'vue'; import { onMounted, ref, provide, computed } from 'vue';
import { useAuthStore } from './stores/auth';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import axios from 'axios'; import axios from 'axios';
console.log('App.vue: Version with auth check loaded'); console.log('App.vue: Version with auth check loaded');
const authStore = useAuthStore();
const router = useRouter(); const router = useRouter();
async function checkAuth() { // Создаем реактивное состояние с помощью ref
try { const authState = ref({
const response = await axios.get('/api/auth/check'); isAuthenticated: false,
userRole: null,
if (response.data.authenticated) { address: null
authStore.setAuth(response.data); });
}
} catch (error) {
console.error('Error checking auth:', error);
}
}
onMounted(checkAuth); // Предоставляем состояние аутентификации всем компонентам
const auth = {
// Используем computed для реактивности
isAuthenticated: computed(() => authState.value.isAuthenticated),
userRole: computed(() => authState.value.userRole),
address: computed(() => authState.value.address),
async checkAuth() {
try {
const response = await axios.get('/api/auth/check');
console.log('Auth check response:', response.data);
authState.value = {
isAuthenticated: response.data.authenticated,
userRole: response.data.role,
address: response.data.address
};
console.log('Auth state updated:', authState.value);
} catch (error) {
console.error('Auth check failed:', error);
}
},
async disconnect() {
try {
await axios.post('/api/auth/logout');
authState.value = {
isAuthenticated: false,
userRole: null,
address: null
};
} catch (error) {
console.error('Logout failed:', error);
}
}
};
provide('auth', auth);
onMounted(async () => {
await auth.checkAuth();
});
</script> </script>
<style> <style>

View File

@@ -1,9 +1,8 @@
import axios from 'axios'; import axios from 'axios';
import { useAuthStore } from '../stores/auth';
// Создаем экземпляр axios с базовым URL // Создаем экземпляр axios с базовым URL
const api = axios.create({ const api = axios.create({
baseURL: '', // Убираем baseURL baseURL: import.meta.env.VITE_API_URL || '',
withCredentials: true, withCredentials: true,
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
@@ -15,10 +14,6 @@ api.interceptors.request.use(
(config) => { (config) => {
config.withCredentials = true; // Важно для каждого запроса config.withCredentials = true; // Важно для каждого запроса
const authStore = useAuthStore();
if (authStore.isAuthenticated && authStore.address) {
config.headers.Authorization = `Bearer ${authStore.address}`;
}
return config; return config;
}, },
(error) => Promise.reject(error) (error) => Promise.reject(error)
@@ -30,15 +25,11 @@ api.interceptors.response.use(
console.log('Response from server:', response.data); console.log('Response from server:', response.data);
return response; return response;
}, },
(error) => { async (error) => {
// Проверяем, что это действительно ошибка авторизации // Проверяем, что это действительно ошибка авторизации
if (error.response?.status === 401 && if (error.response?.status === 401) {
!error.config.url.includes('/auth/') && // Перенаправляем на страницу логина
!error.config.url.includes('/verify') && window.location.href = '/login';
!error.config.url.includes('/chat/history')) { // Не очищаем при ошибке загрузки истории
console.log('Auth error, clearing state');
const auth = useAuthStore();
auth.disconnect();
} }
return Promise.reject(error); return Promise.reject(error);
} }

View File

@@ -1,5 +1,5 @@
<template> <template>
<div v-if="authStore.isAuthenticated"> <div v-if="isAuthenticated">
<div class="conversation-list"> <div class="conversation-list">
<div class="list-header"> <div class="list-header">
<h3>Диалоги</h3> <h3>Диалоги</h3>
@@ -40,20 +40,20 @@
</template> </template>
<script setup> <script setup>
import { ref, onMounted, computed, defineEmits, watch } from 'vue'; import { ref, onMounted, computed, defineEmits, watch, inject } from 'vue';
import { useAuthStore } from '../../stores/auth';
import axios from 'axios'; import axios from 'axios';
const emit = defineEmits(['select-conversation']); const emit = defineEmits(['select-conversation']);
const authStore = useAuthStore(); const auth = inject('auth');
const isAuthenticated = computed(() => auth.isAuthenticated.value);
const conversations = ref([]); const conversations = ref([]);
const loading = ref(true); const loading = ref(true);
const selectedConversationId = ref(null); const selectedConversationId = ref(null);
// Следим за изменением статуса аутентификации // Следим за изменением статуса аутентификации
watch(() => authStore.isAuthenticated, (isAuthenticated) => { watch(() => isAuthenticated.value, (authenticated) => {
if (!isAuthenticated) { if (!authenticated) {
conversations.value = []; // Очищаем список бесед при отключении conversations.value = []; // Очищаем список бесед при отключении
selectedConversationId.value = null; selectedConversationId.value = null;
} }

View File

@@ -1,5 +1,5 @@
<template> <template>
<div v-if="authStore.isAuthenticated"> <div v-if="isAuthenticated">
<div class="message-thread" ref="threadContainer"> <div class="message-thread" ref="threadContainer">
<div v-if="loading" class="loading">Загрузка сообщений...</div> <div v-if="loading" class="loading">Загрузка сообщений...</div>
@@ -28,7 +28,6 @@
<script setup> <script setup>
import { ref, onMounted, watch, nextTick, defineExpose } from 'vue'; import { ref, onMounted, watch, nextTick, defineExpose } from 'vue';
import axios from 'axios'; import axios from 'axios';
import { useAuthStore } from '@/stores/auth'
const props = defineProps({ const props = defineProps({
conversationId: { conversationId: {
@@ -40,7 +39,7 @@ const props = defineProps({
const messages = ref([]); const messages = ref([]);
const loading = ref(true); const loading = ref(true);
const threadContainer = ref(null); const threadContainer = ref(null);
const authStore = useAuthStore() const isAuthenticated = ref(false);
// Загрузка сообщений диалога // Загрузка сообщений диалога
const fetchMessages = async () => { const fetchMessages = async () => {
@@ -112,8 +111,8 @@ watch(
); );
// Следим за изменением статуса аутентификации // Следим за изменением статуса аутентификации
watch(() => authStore.isAuthenticated, (isAuthenticated) => { watch(() => isAuthenticated.value, (authenticated) => {
if (!isAuthenticated) { if (!authenticated) {
messages.value = []; // Очищаем сообщения при отключении messages.value = []; // Очищаем сообщения при отключении
} }
}); });

View File

@@ -36,6 +36,7 @@
<script setup> <script setup>
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
import axios from 'axios';
const props = defineProps({ const props = defineProps({
onEmailAuth: { onEmailAuth: {
@@ -49,6 +50,7 @@ const code = ref('');
const error = ref(''); const error = ref('');
const isLoading = ref(false); const isLoading = ref(false);
const showVerification = ref(false); const showVerification = ref(false);
const isConnecting = ref(false);
const isValidEmail = computed(() => { const isValidEmail = computed(() => {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.value); return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.value);

View File

@@ -18,6 +18,7 @@ import axios from 'axios';
const loading = ref(false); const loading = ref(false);
const error = ref(''); const error = ref('');
const success = ref(''); const success = ref('');
const isConnecting = ref(false);
async function connectTelegram() { async function connectTelegram() {
try { try {

View File

@@ -5,14 +5,17 @@
:disabled="isLoading" :disabled="isLoading"
class="wallet-btn" class="wallet-btn"
> >
{{ isAuthenticated ? 'Подключено' : 'Подключить кошелек' }} {{ isConnected ? 'Подключено' : 'Подключить кошелек' }}
</button> </button>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref } from 'vue'; import { ref, inject, computed } from 'vue';
import { connectWithWallet } from '../../services/wallet'; import { connectWithWallet } from '../../services/wallet';
import { ethers } from 'ethers';
import { SiweMessage } from 'siwe';
import axios from 'axios';
// Определяем props // Определяем props
const props = defineProps({ const props = defineProps({
@@ -24,6 +27,12 @@ const props = defineProps({
// Определяем состояние // Определяем состояние
const isLoading = ref(false); const isLoading = ref(false);
const auth = inject('auth');
const isConnecting = ref(false);
const address = ref('');
// Вычисляемое свойство для статуса подключения
const isConnected = computed(() => auth.isAuthenticated.value);
const emit = defineEmits(['connect']); const emit = defineEmits(['connect']);
@@ -33,22 +42,10 @@ const connectWallet = async () => {
try { try {
isLoading.value = true; isLoading.value = true;
// Получаем адрес кошелька const result = await connectWithWallet();
const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' }); await auth.checkAuth();
const address = accounts[0]; console.log('Wallet connected, auth state:', auth.isAuthenticated.value);
emit('connect', result);
// Получаем nonce
const nonceResponse = await api.get(`/api/auth/nonce?address=${address}`);
const nonce = nonceResponse.data.nonce;
// Подписываем сообщение
const message = `${window.location.host} wants you to sign in with your Ethereum account:\n${address.slice(0, 42)}...`;
const signature = await window.ethereum.request({
method: 'personal_sign',
params: [message, address]
});
emit('connect', { address, signature, message });
} catch (error) { } catch (error) {
console.error('Error connecting wallet:', error); console.error('Error connecting wallet:', error);
} finally { } finally {

View File

@@ -2,20 +2,17 @@ import { Buffer } from 'buffer';
globalThis.Buffer = Buffer; globalThis.Buffer = Buffer;
import { createApp } from 'vue'; import { createApp } from 'vue';
import { createPinia } from 'pinia';
import App from './App.vue'; import App from './App.vue';
import router from './router'; import router from './router';
import axios from 'axios'; import axios from 'axios';
// Настройка axios // Настройка axios
axios.defaults.baseURL = ''; // Пустой baseURL, так как мы используем прокси axios.defaults.baseURL = import.meta.env.VITE_API_URL || '';
axios.defaults.withCredentials = true; // Важно для работы с сессиями axios.defaults.withCredentials = true;
// Создаем и монтируем приложение Vue // Создаем и монтируем приложение Vue
const app = createApp(App); const app = createApp(App);
const pinia = createPinia();
app.use(pinia);
app.use(router); app.use(router);
// Не используем заглушки, так как сервер работает // Не используем заглушки, так как сервер работает
@@ -29,7 +26,7 @@ app.use(router);
// } // }
console.log('API URL:', import.meta.env.VITE_API_URL); console.log('API URL:', import.meta.env.VITE_API_URL);
console.log('main.js: Starting application with router and Pinia'); console.log('main.js: Starting application with router');
app.mount('#app'); app.mount('#app');
console.log('main.js: Application with router and Pinia mounted'); console.log('main.js: Application with router mounted');

View File

@@ -1,6 +1,6 @@
import { createRouter, createWebHistory } from 'vue-router'; import { createRouter, createWebHistory } from 'vue-router';
import HomeView from '../views/HomeView.vue'; import HomeView from '../views/HomeView.vue';
import { useAuthStore } from '../stores/auth'; import axios from 'axios';
console.log('router/index.js: Script loaded'); console.log('router/index.js: Script loaded');
@@ -21,8 +21,6 @@ console.log('router/index.js: Router created');
// Защита маршрутов // Защита маршрутов
router.beforeEach(async (to, from, next) => { router.beforeEach(async (to, from, next) => {
const authStore = useAuthStore();
// Если пытаемся перейти на несуществующий маршрут, перенаправляем на главную // Если пытаемся перейти на несуществующий маршрут, перенаправляем на главную
if (!to.matched.length) { if (!to.matched.length) {
return next({ name: 'home' }); return next({ name: 'home' });
@@ -30,17 +28,19 @@ router.beforeEach(async (to, from, next) => {
// Проверяем аутентификацию, если маршрут требует авторизации // Проверяем аутентификацию, если маршрут требует авторизации
if (to.matched.some(record => record.meta.requiresAuth)) { if (to.matched.some(record => record.meta.requiresAuth)) {
if (!authStore.isAuthenticated) { try {
return next({ name: 'home' }); const response = await axios.get('/api/auth/check');
} if (response.data.authenticated) {
next();
// Проверяем права администратора } else {
if (to.matched.some(record => record.meta.requiresAdmin) && !authStore.isAdmin) { next('/login');
return next({ name: 'home' }); }
} catch (error) {
next('/login');
} }
} else {
next();
} }
next();
}); });
export default router; export default router;

View File

@@ -1,6 +1,5 @@
import { ethers } from 'ethers'; import { ethers } from 'ethers';
import api from '../api/axios'; import axios from 'axios';
import { useAuthStore } from '../stores/auth';
export async function connectWithWallet() { export async function connectWithWallet() {
try { try {
@@ -24,7 +23,7 @@ export async function connectWithWallet() {
console.log('Normalized address:', address); console.log('Normalized address:', address);
console.log('Requesting nonce...'); console.log('Requesting nonce...');
const { data: { nonce } } = await api.get('/api/auth/nonce', { const { data: { nonce } } = await axios.get('/api/auth/nonce', {
params: { address } params: { address }
}); });
console.log('Got nonce:', nonce); console.log('Got nonce:', nonce);
@@ -58,19 +57,17 @@ export async function connectWithWallet() {
console.log('Got signature:', signature); console.log('Got signature:', signature);
console.log('Sending verification request...'); console.log('Sending verification request...');
const response = await api.post('/api/auth/verify', { const response = await axios.post('/api/auth/verify', {
address, address,
signature, signature,
message message
}); });
console.log('Verification response:', response.data); console.log('Verification response:', response.data);
const authStore = useAuthStore(); const provider = new ethers.BrowserProvider(window.ethereum);
if (response.data.authenticated) { const signer = await provider.getSigner();
authStore.setAuth(response.data);
}
return response.data; return { address, signer };
} catch (error) { } catch (error) {
// Форматируем ошибку для пользователя // Форматируем ошибку для пользователя
const message = error.message || 'Ошибка подключения кошелька'; const message = error.message || 'Ошибка подключения кошелька';

View File

@@ -1,515 +0,0 @@
import { defineStore } from 'pinia';
import axios from '../api/axios';
export const useAuthStore = defineStore('auth', {
state: () => ({
user: null,
isAuthenticated: false,
isAdmin: false,
authType: null,
identities: {},
loading: false,
error: null,
messages: [],
address: null,
wallet: null,
telegramId: null,
email: null,
userId: null
}),
actions: {
async connectWallet(address, signature, message) {
this.loading = true;
this.error = null;
try {
const response = await axios.post('/api/chat/verify', {
address,
signature,
message
}, {
withCredentials: true
});
this.user = {
id: response.data.userId,
address: address
};
this.isAuthenticated = response.data.authenticated;
this.isAdmin = response.data.isAdmin;
this.authType = 'wallet';
// Сохраняем адрес кошелька в локальном хранилище
console.log('Saving wallet address to localStorage:', address);
localStorage.setItem('walletAddress', address);
// Связываем гостевые сообщения с аутентифицированным пользователем
try {
await axios.post('/api/chat/link-guest-messages');
console.log('Guest messages linked to authenticated user');
} catch (linkError) {
console.error('Error linking guest messages:', linkError);
}
return {
success: true,
authenticated: response.data.authenticated,
address: address,
isAdmin: response.data.isAdmin,
authType: response.data.authType
};
} catch (error) {
this.error = error.response?.data?.error || 'Ошибка подключения кошелька';
return { success: false, error: this.error };
} finally {
this.loading = false;
}
},
async connectTelegram(telegramData) {
this.loading = true;
this.error = null;
try {
const response = await axios.post('/api/auth/telegram', telegramData);
this.user = {
id: response.data.userId,
telegramId: telegramData.telegramId
};
this.isAuthenticated = response.data.authenticated;
this.isAdmin = response.data.isAdmin;
this.authType = response.data.authType;
this.identities = response.data.identities || {};
return true;
} catch (error) {
this.error = error.response?.data?.error || 'Ошибка подключения Telegram';
return false;
} finally {
this.loading = false;
}
},
async connectEmail(email, verificationCode) {
this.loading = true;
this.error = null;
try {
const response = await axios.post('/api/auth/email', {
email, verificationCode
});
this.user = {
id: response.data.userId,
email
};
this.isAuthenticated = response.data.authenticated;
this.isAdmin = response.data.isAdmin;
this.authType = response.data.authType;
this.identities = response.data.identities || {};
return true;
} catch (error) {
this.error = error.response?.data?.error || 'Ошибка подключения Email';
return false;
} finally {
this.loading = false;
}
},
async linkIdentity(identityType, identityValue) {
this.loading = true;
this.error = null;
try {
const response = await axios.post('/api/auth/link-identity', {
identityType, identityValue
});
this.identities = response.data.identities;
this.isAdmin = response.data.isAdmin;
return true;
} catch (error) {
this.error = error.response?.data?.error || 'Ошибка связывания аккаунта';
return false;
} finally {
this.loading = false;
}
},
async logout() {
try {
await axios.post('/api/auth/logout');
this.user = null;
this.isAuthenticated = false;
this.isAdmin = false;
this.authType = null;
this.identities = {};
this.messages = [];
this.address = null;
// Удаляем адрес из localStorage
localStorage.removeItem('walletAddress');
} catch (error) {
console.error('Ошибка при выходе:', error);
}
},
async checkAuth() {
try {
console.log('Checking auth state...');
const response = await axios.get('/api/auth/check');
console.log('Auth check response:', response.data);
if (response.data.authenticated) {
this.isAuthenticated = true;
this.user = {
id: response.data.userId,
address: response.data.address
};
this.address = response.data.address;
this.isAdmin = response.data.isAdmin;
this.authType = response.data.authType;
return {
authenticated: true,
user: this.user,
address: response.data.address,
isAdmin: response.data.isAdmin,
authType: response.data.authType
};
} else {
this.isAuthenticated = false;
this.user = null;
this.address = null;
this.isAdmin = false;
this.authType = null;
return { authenticated: false };
}
} catch (error) {
console.error('Error checking auth:', error);
this.isAuthenticated = false;
this.user = null;
this.address = null;
this.isAdmin = false;
this.authType = null;
return { authenticated: false };
}
},
async refreshSession() {
try {
// Если есть адрес в localStorage, используем его
const storedAddress = localStorage.getItem('walletAddress');
const response = await axios.post('/api/auth/refresh-session', {
address: storedAddress || this.address
}, {
withCredentials: true
});
return response.data.success;
} catch (error) {
console.error('Error refreshing session:', error);
return false;
}
},
async checkWalletConnection() {
// Проверяем, есть ли сохраненный адрес кошелька
const savedAddress = localStorage.getItem('walletAddress');
console.log('Checking for saved wallet address:', savedAddress);
if (savedAddress) {
try {
// Проверяем, доступен ли провайдер Ethereum (MetaMask)
if (window.ethereum) {
// Запрашиваем доступ к аккаунтам
const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
const currentAddress = accounts[0].toLowerCase();
console.log('Current connected address:', currentAddress);
console.log('Saved address:', savedAddress.toLowerCase());
// Проверяем, совпадает ли текущий адрес с сохраненным
if (currentAddress === savedAddress.toLowerCase()) {
console.log('Wallet address matches, restoring session');
// Восстанавливаем состояние аутентификации
this.user = {
id: null, // ID будет получен при проверке аутентификации
address: savedAddress
};
// Проверяем аутентификацию на сервере
const authResult = await this.checkAuth();
if (authResult.authenticated) {
console.log('Session restored successfully');
return true;
}
} else {
console.log('Connected wallet address does not match saved address');
localStorage.removeItem('walletAddress');
}
}
} catch (error) {
console.error('Error restoring wallet connection:', error);
}
}
return false;
},
async requestEmailVerification(email) {
this.loading = true;
this.error = null;
try {
const response = await axios.post('/api/auth/email/request', { email });
console.log('Email verification code response:', response.data);
return {
success: true,
message: response.data.message,
verificationCode: response.data.verificationCode // Для разработки
};
} catch (error) {
this.error = error.response?.data?.error || 'Ошибка запроса кода';
return { success: false, error: this.error };
} finally {
this.loading = false;
}
},
async verifyEmail(code) {
this.loading = true;
this.error = null;
try {
const response = await axios.post('/api/auth/email/verify', { code });
if (response.data.success) {
this.isAuthenticated = true;
this.user = {
id: response.data.userId,
email: response.data.email
};
if (response.data.walletAddress) {
this.user.address = response.data.walletAddress;
this.address = response.data.walletAddress;
}
this.isAdmin = response.data.isAdmin;
this.authType = 'email';
}
return response.data;
} catch (error) {
this.error = error.response?.data?.error || 'Ошибка верификации';
return { success: false, error: this.error };
} finally {
this.loading = false;
}
},
async requestTelegramCode() {
this.loading = true;
this.error = null;
try {
const response = await axios.get('/api/auth/telegram/code');
return response.data;
} catch (error) {
this.error = error.response?.data?.error || 'Ошибка запроса кода';
return { success: false, error: this.error };
} finally {
this.loading = false;
}
},
async verifyTelegram(telegramId, code) {
this.loading = true;
this.error = null;
try {
const response = await axios.post('/api/auth/telegram/verify', { telegramId, code });
if (response.data.success) {
this.isAuthenticated = true;
this.user = {
id: response.data.userId,
telegramId: response.data.telegramId
};
if (response.data.walletAddress) {
this.user.address = response.data.walletAddress;
this.address = response.data.walletAddress;
}
this.isAdmin = response.data.isAdmin;
this.authType = 'telegram';
}
return response.data;
} catch (error) {
this.error = error.response?.data?.error || 'Ошибка верификации';
return { success: false, error: this.error };
} finally {
this.loading = false;
}
},
async linkIdentity(type, value) {
this.loading = true;
this.error = null;
try {
const response = await axios.post('/api/auth/link-identity', { type, value });
if (response.data.success) {
if (type === 'wallet') {
this.user.address = value;
this.address = value;
} else if (type === 'email') {
this.user.email = value;
} else if (type === 'telegram') {
this.user.telegramId = value;
}
this.isAdmin = response.data.isAdmin;
}
return response.data;
} catch (error) {
this.error = error.response?.data?.error || 'Ошибка связывания аккаунта';
return { success: false, error: this.error };
} finally {
this.loading = false;
}
},
async createTelegramAuthToken() {
this.loading = true;
this.error = null;
try {
const response = await axios.post('/api/auth/telegram/auth-token');
return response.data;
} catch (error) {
this.error = error.response?.data?.error || 'Ошибка создания токена';
return { success: false, error: this.error };
} finally {
this.loading = false;
}
},
async checkTelegramAuthStatus(token) {
try {
const response = await axios.get(`/api/auth/telegram/auth-status/${token}`);
if (response.data.success && response.data.authenticated) {
// Обновляем состояние аутентификации
await this.checkAuth();
}
return response.data;
} catch (error) {
console.error('Error checking Telegram auth status:', error);
return { success: false, error: 'Ошибка проверки статуса' };
}
},
async disconnect() {
try {
// Очищаем сессию на сервере
await axios.post('/api/auth/clear-session');
// Очищаем состояние
this.isAuthenticated = false;
this.userId = null;
this.address = null;
this.isAdmin = false;
this.authType = null;
// Очищаем локальное хранилище
localStorage.removeItem('auth');
} catch (error) {
console.error('Error during disconnect:', error);
}
},
// Выносим очистку состояния в отдельный метод
clearState() {
this.isAuthenticated = false;
this.wallet = null;
this.messages = [];
this.user = null;
this.address = null;
this.isAdmin = false;
this.authType = null;
this.identities = {};
this.telegramId = null;
this.userId = null;
// Очищаем локальное хранилище
localStorage.removeItem('wallet');
localStorage.removeItem('isAuthenticated');
localStorage.removeItem('walletAddress');
localStorage.removeItem('auth');
},
async setWalletAuth(authData) {
this.isAuthenticated = authData.authenticated;
this.address = authData.address;
this.isAdmin = authData.isAdmin;
},
async setTelegramAuth(authData) {
this.telegramId = authData.telegramId;
// Проверяем баланс токенов если есть подключенный кошелек
if (this.address) {
await this.checkTokenBalance();
}
},
async checkTokenBalance() {
try {
const response = await axios.get(`/api/auth/check-tokens?address=${this.address}`);
this.isAdmin = response.data.isAdmin;
} catch (error) {
console.error('Error checking token balance:', error);
}
},
setAuth(authData) {
console.log('Setting auth state:', authData);
// Обновляем только состояние в памяти
this.isAuthenticated = authData.authenticated || authData.isAuthenticated;
this.user = {
id: authData.userId,
address: authData.address
};
this.userId = authData.userId;
this.isAdmin = authData.isAdmin;
this.authType = authData.authType;
this.address = authData.address;
console.log('Auth state updated:', {
isAuthenticated: this.isAuthenticated,
userId: this.userId,
authType: this.authType,
address: this.address,
isAdmin: this.isAdmin
});
}
}
});

View File

@@ -2,7 +2,7 @@
<div class="home"> <div class="home">
<h1> HB3 - Accelerator DLE (Digital Legal Entity - DAO Fork)</h1> <h1> HB3 - Accelerator DLE (Digital Legal Entity - DAO Fork)</h1>
<div class="auth-section" v-if="!auth.isAuthenticated"> <div class="auth-section" v-if="!isAuthenticated">
<h3>Венчурный фонд и поставщик программного обеспечения</h3> <h3>Венчурный фонд и поставщик программного обеспечения</h3>
</div> </div>
@@ -10,13 +10,13 @@
<div class="chat-container"> <div class="chat-container">
<div class="chat-header"> <div class="chat-header">
<!-- Используем тот же компонент, что и в сообщениях --> <!-- Используем тот же компонент, что и в сообщениях -->
<div v-if="!auth.isAuthenticated" class="auth-buttons"> <div v-if="!isAuthenticated" class="auth-buttons">
<button class="auth-btn wallet-btn" @click="handleWalletAuth"> <button class="auth-btn wallet-btn" @click="handleWalletAuth">
<span class="auth-icon">👛</span> Подключить кошелек <span class="auth-icon">👛</span> Подключить кошелек
</button> </button>
</div> </div>
<div v-else class="wallet-info"> <div v-else class="wallet-info">
<span>{{ truncateAddress(auth.address) }}</span> <span>{{ truncateAddress(auth.address.value) }}</span>
<button class="disconnect-btn" @click="disconnectWallet"> <button class="disconnect-btn" @click="disconnectWallet">
Отключить кошелек Отключить кошелек
</button> </button>
@@ -24,7 +24,7 @@
</div> </div>
<!-- Кнопка загрузки предыдущих сообщений --> <!-- Кнопка загрузки предыдущих сообщений -->
<div v-if="auth.isAuthenticated && hasMoreMessages" class="load-more"> <div v-if="isAuthenticated && hasMoreMessages" class="load-more">
<button @click="loadMoreMessages" :disabled="isLoadingMore"> <button @click="loadMoreMessages" :disabled="isLoadingMore">
{{ isLoadingMore ? 'Загрузка...' : 'Показать предыдущие сообщения' }} {{ isLoadingMore ? 'Загрузка...' : 'Показать предыдущие сообщения' }}
</button> </button>
@@ -40,7 +40,7 @@
</div> </div>
<!-- Кнопки аутентификации --> <!-- Кнопки аутентификации -->
<div v-if="message.showAuthButtons && !auth.isAuthenticated" class="auth-buttons"> <div v-if="message.showAuthButtons && !isAuthenticated" class="auth-buttons">
<button class="auth-btn wallet-btn" @click="handleWalletAuth"> <button class="auth-btn wallet-btn" @click="handleWalletAuth">
<span class="auth-icon">👛</span> Подключить кошелек <span class="auth-icon">👛</span> Подключить кошелек
</button> </button>
@@ -117,16 +117,18 @@
</template> </template>
<script setup> <script setup>
import { ref, computed, onMounted, watch, nextTick, onBeforeUnmount } from 'vue'; import { ref, computed, onMounted, watch, nextTick, onBeforeUnmount, inject } from 'vue';
import { useAuthStore } from '../stores/auth';
import WalletConnection from '../components/identity/WalletConnection.vue'; import WalletConnection from '../components/identity/WalletConnection.vue';
import TelegramConnect from '../components/identity/TelegramConnect.vue'; import TelegramConnect from '../components/identity/TelegramConnect.vue';
import EmailConnect from '../components/identity/EmailConnect.vue';
import api from '../api/axios'; import api from '../api/axios';
import { connectWithWallet } from '../services/wallet'; import { connectWithWallet } from '../services/wallet';
console.log('HomeView.vue: Version with chat loaded'); console.log('HomeView.vue: Version with chat loaded');
const auth = useAuthStore(); const auth = inject('auth');
const isAuthenticated = computed(() => auth.isAuthenticated.value);
const authType = ref(null);
const messages = ref([]); const messages = ref([]);
const guestMessages = ref([]); const guestMessages = ref([]);
const newMessage = ref(''); const newMessage = ref('');
@@ -181,7 +183,7 @@ const scrollToBottom = () => {
// Загрузка сообщений // Загрузка сообщений
const loadMoreMessages = async () => { const loadMoreMessages = async () => {
if (!auth.isAuthenticated) return; if (!isAuthenticated.value) return;
try { try {
isLoadingMore.value = true; isLoadingMore.value = true;
@@ -213,7 +215,7 @@ const loadMoreMessages = async () => {
}; };
// Загружаем сообщения при изменении аутентификации // Загружаем сообщения при изменении аутентификации
watch(() => auth.isAuthenticated, async (newValue) => { watch(() => isAuthenticated.value, async (newValue) => {
if (newValue) { if (newValue) {
messages.value = []; messages.value = [];
offset.value = 0; offset.value = 0;
@@ -244,12 +246,13 @@ watch(() => auth.isAuthenticated, async (newValue) => {
} }
}); });
// Функция для подключения кошелька // Находим существующую функцию handleWalletAuth и обновляем её
const handleWalletAuth = async () => { const handleWalletAuth = async () => {
try { try {
const result = await connectWithWallet(); const result = await connectWithWallet();
await auth.checkAuth();
if (result.success) { if (result.authenticated) {
// Сохраняем гостевые сообщения перед очисткой // Сохраняем гостевые сообщения перед очисткой
const guestMessages = [...messages.value]; const guestMessages = [...messages.value];
messages.value = []; messages.value = [];
@@ -432,7 +435,7 @@ const handleMessage = async (text) => {
newMessage.value = ''; newMessage.value = '';
isLoading.value = true; isLoading.value = true;
if (!auth.isAuthenticated) { if (!isAuthenticated.value) {
// Сохраняем в таблицу guest_messages // Сохраняем в таблицу guest_messages
const response = await api.post('/api/chat/guest-message', { const response = await api.post('/api/chat/guest-message', {
message: messageContent, message: messageContent,

View File

@@ -38,10 +38,10 @@ export default defineConfig({
}, },
server: { server: {
port: 5173, port: 5173,
host: 'localhost', host: '127.0.0.1',
proxy: { proxy: {
'/api': { '/api': {
target: 'http://localhost:8000', target: 'http://127.0.0.1:8000',
changeOrigin: true, changeOrigin: true,
secure: false, secure: false,
credentials: true, credentials: true,