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

This commit is contained in:
2025-02-21 19:08:03 +03:00
parent 2ddd4a9ff0
commit 1f08f07ca5
11 changed files with 248 additions and 20 deletions

View File

@@ -0,0 +1,21 @@
-- Создаем временную таблицу для уникальных адресов
CREATE TEMP TABLE unique_users AS
SELECT DISTINCT ON (LOWER(address))
id,
LOWER(address) as address,
created_at
FROM users
ORDER BY LOWER(address), created_at ASC;
-- Удаляем все записи из users
TRUNCATE users CASCADE;
-- Восстанавливаем уникальные записи
INSERT INTO users (id, address, created_at)
SELECT id, address, created_at FROM unique_users;
-- Обновляем последовательность id
SELECT setval('users_id_seq', (SELECT MAX(id) FROM users));
-- Удаляем временную таблицу
DROP TABLE unique_users;

View File

@@ -30,3 +30,11 @@ CREATE TABLE IF NOT EXISTS chat_history (
-- Даем права пользователю -- Даем права пользователю
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO postgres; GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO postgres;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO postgres; GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO postgres;
-- Обновляем существующие адреса
UPDATE users SET address = LOWER(address);
-- Удаляем дубликаты
DELETE FROM users a USING users b
WHERE a.id > b.id
AND LOWER(a.address) = LOWER(b.address);

View File

@@ -130,7 +130,7 @@ router.post('/chat', requireAuth, async (req, res) => {
// Получаем или создаем пользователя // Получаем или создаем пользователя
let user = await pool.query( let user = await pool.query(
'INSERT INTO users (address) VALUES ($1) ON CONFLICT (address) DO UPDATE SET address = EXCLUDED.address RETURNING id', 'INSERT INTO users (address) VALUES (LOWER($1)) ON CONFLICT (address) DO UPDATE SET address = LOWER($1) RETURNING id',
[userAddress] [userAddress]
); );
const userId = user.rows[0].id; const userId = user.rows[0].id;
@@ -166,34 +166,36 @@ router.post('/chat', requireAuth, async (req, res) => {
router.get('/chat/history', requireAuth, async (req, res) => { router.get('/chat/history', requireAuth, async (req, res) => {
try { try {
const userAddress = req.session.siwe.address; const userAddress = req.session.siwe.address;
console.log('Запрос истории чата для:', userAddress);
// Получаем ID пользователя // Получаем ID пользователя
const userResult = await pool.query( const userResult = await pool.query(
'SELECT id FROM users WHERE address = $1', 'SELECT id FROM users WHERE LOWER(address) = LOWER($1) ORDER BY created_at ASC LIMIT 1',
[userAddress] [userAddress]
); );
console.log('Найден пользователь:', userResult.rows);
if (userResult.rows.length === 0) {
return res.status(404).json({ error: 'User not found' });
}
const userId = userResult.rows[0].id; const userId = userResult.rows[0].id;
// Получаем историю чата // Получаем историю чата
const history = await pool.query( const history = await pool.query(
`SELECT * FROM ( `SELECT
SELECT ch.id,
message as content, LOWER(u.address) as address,
created_at, ch.message,
'user' as role ch.response,
FROM chat_history ch.created_at
WHERE user_id = $1 FROM chat_history ch
UNION ALL JOIN users u ON ch.user_id = u.id
SELECT WHERE ch.user_id = $1
response as content, ORDER BY created_at DESC`,
created_at,
'assistant' as role
FROM chat_history
WHERE user_id = $1
) messages
ORDER BY created_at ASC`,
[userId] [userId]
); );
console.log('История чата:', history.rows);
res.json({ res.json({
history: history.rows history: history.rows
@@ -204,4 +206,19 @@ router.get('/chat/history', requireAuth, async (req, res) => {
} }
}); });
// Получение списка пользователей
router.get('/users', requireAuth, async (req, res) => {
try {
console.log('Запрос списка пользователей');
const users = await pool.query(
'SELECT id, LOWER(address) as address, created_at FROM users ORDER BY created_at DESC'
);
console.log('Найдено пользователей:', users.rows);
res.json({ users: users.rows });
} catch (error) {
console.error('Ошибка получения пользователей:', error);
res.status(500).json({ error: 'Ошибка сервера' });
}
});
module.exports = router; module.exports = router;

View File

@@ -1 +1 @@
{"cookie":{"originalMaxAge":2591999999,"expires":"2025-03-23T14:05:28.613Z","secure":false,"httpOnly":true,"domain":"127.0.0.1","path":"/","sameSite":"lax"},"nonce":null,"__lastAccess":1740146728614,"siwe":{"domain":"127.0.0.1:5173","address":"0xF45aa4917b3775bA37f48Aeb3dc1a943561e9e0B","statement":"Sign in with Ethereum to access DApp features and AI Assistant","uri":"http://127.0.0.1:5173","version":"1","nonce":"KF6YuwJBLPv8vFKCK","issuedAt":"2025-02-21T14:05:01.684Z","chainId":11155111,"resources":["http://127.0.0.1:5173/api/chat","http://127.0.0.1:5173/api/contract"]},"authenticated":true} {"cookie":{"originalMaxAge":2591999999,"expires":"2025-03-23T16:06:19.831Z","secure":false,"httpOnly":true,"domain":"127.0.0.1","path":"/","sameSite":"lax"},"nonce":null,"__lastAccess":1740153979832,"siwe":{"domain":"127.0.0.1:5173","address":"0xF45aa4917b3775bA37f48Aeb3dc1a943561e9e0B","statement":"Sign in with Ethereum to access DApp features and AI Assistant","uri":"http://127.0.0.1:5173","version":"1","nonce":"u7P3wT2kyPmGb4Z0I","issuedAt":"2025-02-21T16:05:49.561Z","chainId":11155111,"resources":["http://127.0.0.1:5173/api/chat","http://127.0.0.1:5173/api/contract"]},"authenticated":true}

View File

@@ -0,0 +1 @@
{"cookie":{"originalMaxAge":2592000000,"expires":"2025-03-23T14:54:20.531Z","secure":false,"httpOnly":true,"domain":"127.0.0.1","path":"/","sameSite":"lax"},"__lastAccess":1740149660531}

View File

@@ -0,0 +1 @@
{"cookie":{"originalMaxAge":2592000000,"expires":"2025-03-23T14:53:08.151Z","secure":false,"httpOnly":true,"domain":"127.0.0.1","path":"/","sameSite":"lax"},"__lastAccess":1740149588151}

View File

@@ -0,0 +1 @@
{"cookie":{"originalMaxAge":2592000000,"expires":"2025-03-23T14:36:28.622Z","secure":false,"httpOnly":true,"domain":"127.0.0.1","path":"/","sameSite":"lax"},"__lastAccess":1740148588623}

View File

@@ -0,0 +1 @@
{"cookie":{"originalMaxAge":2592000000,"expires":"2025-03-23T14:49:54.299Z","secure":false,"httpOnly":true,"domain":"127.0.0.1","path":"/","sameSite":"lax"},"__lastAccess":1740149394299}

View File

@@ -5,8 +5,14 @@
<AIAssistant <AIAssistant
:isConnected="isConnected" :isConnected="isConnected"
:userAddress="userAddress" :userAddress="userAddress"
@chatUpdated="handleChatUpdate"
/> />
<ServerControl /> <ServerControl />
<DataTables
:isConnected="isConnected"
:userAddress="userAddress"
ref="dataTables"
/>
</div> </div>
</template> </template>
@@ -15,10 +21,12 @@ import { ref, watch } from 'vue'
import ContractInteraction from './components/ContractInteraction.vue' import ContractInteraction from './components/ContractInteraction.vue'
import AIAssistant from './components/AIAssistant.vue' import AIAssistant from './components/AIAssistant.vue'
import ServerControl from './components/ServerControl.vue' import ServerControl from './components/ServerControl.vue'
import DataTables from './components/DataTables.vue'
const contractInteraction = ref(null) const contractInteraction = ref(null)
const isConnected = ref(false) const isConnected = ref(false)
const userAddress = ref(null) const userAddress = ref(null)
const dataTables = ref(null)
watch(() => contractInteraction.value?.isConnected, (newValue) => { watch(() => contractInteraction.value?.isConnected, (newValue) => {
isConnected.value = newValue isConnected.value = newValue
@@ -27,6 +35,10 @@ watch(() => contractInteraction.value?.isConnected, (newValue) => {
watch(() => contractInteraction.value?.address, (newValue) => { watch(() => contractInteraction.value?.address, (newValue) => {
userAddress.value = newValue userAddress.value = newValue
}) })
function handleChatUpdate() {
dataTables.value?.fetchData()
}
</script> </script>
<style> <style>

View File

@@ -35,6 +35,8 @@ const props = defineProps({
userAddress: String userAddress: String
}) })
const emit = defineEmits(['chatUpdated'])
const userInput = ref('') const userInput = ref('')
const messages = ref([]) const messages = ref([])
const isLoading = ref(false) const isLoading = ref(false)
@@ -129,6 +131,7 @@ async function sendMessage() {
messages.value.push({ role: 'user', content: userInput.value }) messages.value.push({ role: 'user', content: userInput.value })
messages.value.push({ role: 'assistant', content: data.response }) messages.value.push({ role: 'assistant', content: data.response })
userInput.value = '' userInput.value = ''
emit('chatUpdated')
} catch (error) { } catch (error) {
console.error('Ошибка при отправке сообщения:', error) console.error('Ошибка при отправке сообщения:', error)
messages.value.push({ messages.value.push({

View File

@@ -0,0 +1,163 @@
<template>
<div class="data-tables" v-if="isConnected">
<h3>Данные из базы</h3>
<!-- История чатов -->
<div class="table-section">
<h4>История чатов</h4>
<table>
<thead>
<tr>
<th>Адрес</th>
<th>Сообщение</th>
<th>Ответ</th>
<th>Дата</th>
</tr>
</thead>
<tbody>
<tr v-for="chat in chatHistory" :key="chat.id">
<td>{{ shortenAddress(chat.address) }}</td>
<td>{{ chat.message }}</td>
<td>{{ chat.response }}</td>
<td>{{ formatDate(chat.created_at) }}</td>
</tr>
</tbody>
</table>
</div>
<!-- Пользователи -->
<div class="table-section">
<h4>Пользователи</h4>
<table>
<thead>
<tr>
<th>ID</th>
<th>Адрес</th>
<th>Дата регистрации</th>
</tr>
</thead>
<tbody>
<tr v-for="user in users" :key="user.id">
<td>{{ user.id }}</td>
<td>{{ shortenAddress(user.address) }}</td>
<td>{{ formatDate(user.created_at) }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, watch } from 'vue';
const props = defineProps({
isConnected: Boolean,
userAddress: String
});
const chatHistory = ref([]);
const users = ref([]);
// Нормализация адреса (приведение к нижнему регистру)
function normalizeAddress(address) {
return address?.toLowerCase() || '';
}
// Следим за изменением состояния подключения
watch(() => props.isConnected, (newValue) => {
console.log('isConnected изменился:', newValue);
if (newValue) {
fetchData();
}
});
// Следим за изменением адреса
watch(() => props.userAddress, (newValue) => {
console.log('userAddress изменился:', newValue);
if (props.isConnected && newValue) {
fetchData();
}
});
// Получение данных
async function fetchData() {
try {
console.log('Запрос обновления данных');
// История чатов
const chatResponse = await fetch('http://127.0.0.1:3000/api/chat/history', {
credentials: 'include'
});
const chatData = await chatResponse.json();
console.log('Получена история чата:', chatData);
chatHistory.value = chatData.history.map(chat => ({
...chat,
address: normalizeAddress(chat.address)
}));
// Пользователи
const usersResponse = await fetch('http://127.0.0.1:3000/api/users', {
credentials: 'include'
});
const usersData = await usersResponse.json();
console.log('Получен список пользователей:', usersData);
users.value = usersData.users.map(user => ({
...user,
address: normalizeAddress(user.address)
}));
} catch (error) {
console.error('Ошибка получения данных:', error);
}
}
// Форматирование адреса
function shortenAddress(address) {
return `${address.slice(0, 6)}...${address.slice(-4)}`;
}
// Форматирование даты
function formatDate(date) {
return new Date(date).toLocaleString();
}
onMounted(() => {
if (props.isConnected) {
fetchData();
}
});
// Делаем метод доступным извне
defineExpose({
fetchData
});
</script>
<style scoped>
.data-tables {
margin: 20px;
}
.table-section {
margin-bottom: 30px;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
}
th, td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
th {
background-color: #f5f5f5;
}
tr:nth-child(even) {
background-color: #f9f9f9;
}
</style>