Описание изменений
This commit is contained in:
21
backend/migrations/cleanup.sql
Normal file
21
backend/migrations/cleanup.sql
Normal 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;
|
||||||
@@ -29,4 +29,12 @@ 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);
|
||||||
@@ -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;
|
||||||
@@ -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}
|
||||||
1
backend/sessions/OgJhv73wKRJ4xuAAUBa9k5CtZ9E4dcFJ.json
Normal file
1
backend/sessions/OgJhv73wKRJ4xuAAUBa9k5CtZ9E4dcFJ.json
Normal 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}
|
||||||
1
backend/sessions/P6j78qGOxf6uv3j-AGfgu0la3luxKbaV.json
Normal file
1
backend/sessions/P6j78qGOxf6uv3j-AGfgu0la3luxKbaV.json
Normal 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}
|
||||||
1
backend/sessions/VO2T1gK1S6E0sq7-yYtjHx6p3nsixeGv.json
Normal file
1
backend/sessions/VO2T1gK1S6E0sq7-yYtjHx6p3nsixeGv.json
Normal 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}
|
||||||
1
backend/sessions/jK4QzdazTk9W_ubXZTLnIEwgGD2xcwmE.json
Normal file
1
backend/sessions/jK4QzdazTk9W_ubXZTLnIEwgGD2xcwmE.json
Normal 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}
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
163
frontend/src/components/DataTables.vue
Normal file
163
frontend/src/components/DataTables.vue
Normal 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>
|
||||||
Reference in New Issue
Block a user